• Vui lòng đọc nội qui diễn đàn để tránh bị xóa bài viết
  • Tìm kiếm trước khi đặt câu hỏi

SetWindowLong, GetWindowLong và Subclassing

Các bài viết hướng dẫn, giúp các bạn hiểu và tiếp cận với Visual Basic nhanh hơn
Hình đại diện của người dùng
NoBi
Quản trị
Quản trị
Bài viết: 957
Ngày tham gia: T.Ba 18/03/2008 1:22 pm
Đến từ: Sài Gòn
Has thanked: 53 time
Been thanked: 66 time
Liên hệ:

SetWindowLong, GetWindowLong và Subclassing

Gửi bàigửi bởi NoBi » T.Sáu 28/03/2008 12:12 pm

Tên bài viết: Hướng dẫn sử dụng sơ lược hàm API SetWindowLong và GetWindowLong
Tác giả: NoBi
Cấp độ bài viết: Chưa đánh giá
Tóm tắt: Hướng dẫn sử dụng sơ lược hàm API SetWindowLong, GetWindowLong và kỷ thuật Subclassing


Sau SendMessage, tôi xin giới thiệu đến các bạn 1 cặp hàm cũng khá quan trọng là SetWindowLongGetWindowLong.

Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Thay đổi thông tin (32 bit – Long) thuộc tính của 1 cửa sổ.

  • hWnd
    Handle của cửa sổ.
  • nIndex
    Giá trị qui định thông tin muốn thay đổi, gồm 1 trong các hằng số sau:
    GWL_EXSTYLE
    Đặt lại kiểu của cửa sổ mở rộng.
    GWL_STYLE
    Đặt lại kiểu của cửa sổ.
    GWL_WNDPROC
    Đặt lại địa chỉ cho thủ tục callback của cửa sổ (the window procedure).
    GWL_HINSTANCE
    Sets a new application instance handle.
    GWL_ID
    Sets a new identifier of the window.
    GWL_USERDATA
    Sets the 32-bit value associated with the window. Each window has a corresponding 32-bit value intended for use by the application that created the window.

    The following values are also available when the hWnd parameter identifies a dialog box:
    DWL_DLGPROC
    Sets the new address of the dialog box procedure.
    DWL_MSGRESULT
    Sets the return value of a message processed in the dialog box procedure.
    DWL_USER
    Sets new extra information that is private to the application, such as handles or pointers.

  • dwNewLong
    Giá trị muốn thay thế.


Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long

Lấy thông tin (32 bit - Long) của 1 cửa sổ.

  • hWnd
    Handle của cửa sổ cần lấy thông tin.
  • nIndex
    Giá trị qui định thông tin muốn lấy, gồm 1 trong các hằng số sau:
    GWL_EXSTYLE
    Lấy thông tin kiểu của cửa sổ mở rộng.
    GWL_STYLE
    Lấy thông tin kiểu của cửa sổ.
    GWL_WNDPROC
    Lấy địa chỉ thủ tục callback của cửa sổ. Bạn phải sử dụng hàm CallWindowProc để gọi “window procedure”.
    GWL_HINSTANCE
    Retrieves the handle of the application instance.
    GWL_HWNDPARENT
    Retrieves the handle of the parent window, if any.
    GWL_ID
    Retrieves the identifier of the window.
    GWL_USERDATA
    Retrieves the 32-bit value associated with the window. Each window has a corresponding 32-bit value intended for use by the application that created the window.

    The following values are also available when the hWnd parameter identifies a dialog box:
    DWL_DLGPROC
    Retrieves the address of the dialog box procedure, or a handle representing the address of the dialog box procedure. You must use the CallWindowProc function to call the dialog box procedure.
    DWL_MSGRESULT
    Retrieves the return value of a message processed in the dialog box procedure.
    DWL_USER
    Retrieves extra information private to the application, such as handles or pointers.


Ví dụ sau đây mô tả cách dùng cặp hàm trên để đặt lại các style cho cửa sổ khác:

Mã: Chọn hết

  1. 'Tạo mới 1 project, thêm vào Form 1 TextBox
  2.  
  3. Option Explicit
  4.  
  5. 'Khai báo sử dụng 2 hàm API
  6. Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
  7. Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  8.  
  9. 'Hằng số dùng cho thay đổi style
  10. Const GWL_STYLE = (-16)
  11. Const ES_NUMBER = &H2000&
  12.  
  13. 'Hằng số dùng cho thay đổi style mở rộng
  14. Private Const GWL_EXSTYLE = (-20)
  15. Private Const WS_EX_APPWINDOW = &H40000
  16.  
  17. 'Thủ tục set TextBox chỉ cho nhập số
  18. Public Sub SetNumber(NumberText As TextBox, Flag As Boolean)
  19.     Dim curstyle As Long, newstyle As Long
  20.  
  21.     'Lấy style hiện tại của TextBox
  22.     curstyle = GetWindowLong(NumberText.hwnd, GWL_STYLE)
  23.  
  24.     If Flag Then
  25.         'Thêm chỉ thị chỉ cho nhập số vào TextBox
  26.         newstyle = curstyle Or ES_NUMBER
  27.     Else
  28.         'Bỏ chỉ thị chỉ cho nhập số vào TextBox
  29.         newstyle = curstyle And (Not ES_NUMBER)
  30.     End If
  31.  
  32.     'Đặt lại style mới cho TextBox
  33.     newstyle = SetWindowLong(NumberText.hwnd, GWL_STYLE, newstyle)
  34.    
  35.     'refresh
  36.     NumberText.Refresh
  37. End Sub
  38.  
  39.  
  40. 'Thủ tục cho hiện Form ở dưới Taskbar
  41. Public Sub Show_In_Taskbar(hwnd As Long, Flag As Boolean)
  42.     Dim curstyleEx As Long, newstyleEx As Long
  43.  
  44.     'Lấy style mở rộng hiện có của cửa sổ
  45.     curstyleEx = GetWindowLong(hwnd, GWL_EXSTYLE)
  46.  
  47.     If Flag Then
  48.         'Thêm vào exstyle hiện có 1 exstyle mới cho phép cửa sổ nằm dưới TaskBar
  49.         newstyleEx = curstyleEx Or WS_EX_APPWINDOW
  50.     Else
  51.         'Bỏ đi exstyle cho phép cửa sổ nằm dưới TaskBar nếu có
  52.         newstyleEx = curstyleEx And Not WS_EX_APPWINDOW
  53.     End If
  54.        
  55.     'Đặt lại exstyle mới
  56.     SetWindowLong hwnd, GWL_EXSTYLE, newstyleEx
  57.    
  58. End Sub
  59.  
  60.  
  61. Private Sub Form_Load()
  62.     Me.Caption = "Now, try typing some letters into the textbox"
  63.    
  64.     'Làm TextBox chỉ cho nhập số
  65.     SetNumber Text1, True
  66.    
  67.     'Không cho hiển thị from dưới taskbar
  68.     Show_In_Taskbar Me.hwnd, False
  69.    
  70. End Sub


Do WS_EX_APPWINDOW là 1 extension style (style mở rộng) nên chúng ta phải lấy và đặt lại nó theo hằng số dành cho ExStyle là GWL_EXSTYLE. Tương tự ES_NUMBER là 1 style bình thường nên sẽ được dùng với hằng số GWL_STYLE.
Thông tin về các hằng số của style: http://www.autoitscript.com/autoit3/doc ... Styles.htm


:>

Hình đại diện của người dùng
NoBi
Quản trị
Quản trị
Bài viết: 957
Ngày tham gia: T.Ba 18/03/2008 1:22 pm
Đến từ: Sài Gòn
Has thanked: 53 time
Been thanked: 66 time
Liên hệ:

Re: SetWindowLong, GetWindowLong và Subclassing

Gửi bàigửi bởi NoBi » T.Tư 04/02/2009 6:10 pm

Ở bài viết trên, chúng ta đã được làm quen sơ bộ về cặp hàm GetWindowLong và SetWindowLong cùng ví dụ thay đổi style của cửa sổ với 2 hằng số GWL_EXSTYLE và GWL_STYLE. Ở bài viết này, tôi xin giới thiệu cách sử dụng hằng GWL_WNDPROC được dùng cho kỷ thuật Subclassing.

Kỷ thuật Subclassing là gì?
Khi chúng ta mở 1 ứng dụng, hệ điều hành Windows sẽ cấp phát cho cửa sổ đó và các control trong nó (gọi chung là các cửa sổ) mỗi cái 1 handle để quản lý. Và khi chúng ta tương tác với các cửa sổ đó (click vào button, gõ chử trong textbox, minimize form…), hđh sẽ gửi tới các cửa sổ đó những thông điệp (message) tương ứng để chúng xử lý. Việc dùng kỷ thuật để chặn các thông điệp được gửi tới này, xử lý theo ý của mình thì được gọi là Subclassing.

Ứng dụng của Subclassing?
Được dùng trong các trường hợp không thể hoặc khó thực hiện được bằng các đoạn code bình thường, bắt buộc phải chặn message để xử lý. Ví dụ như đoạn code không cho phép resize Form sau:
Bình thường:

Mã: Chọn hết

  1. Dim w, h
  2. Private Sub Form_Load()
  3.     w = Me.Width
  4.     h = Me.Height
  5. End Sub
  6. Private Sub Form_Resize()
  7.     Me.Height = h
  8.     Me.Width = w
  9. End Sub


Subclassing:
Form code:

Mã: Chọn hết

  1. Private Sub Form_Load()
  2.     ' Subclass
  3.     lngOldProc = SetWindowLong(Me.hwnd, GWL_WNDPROC, AddressOf myWindowProc)
  4. End Sub
  5.  
  6. Private Sub Form_Unload(Cancel As Integer)
  7.     ' unSubclass
  8.     SetWindowLong Me.hwnd, GWL_WNDPROC, lngOldProc
  9. End Sub


Module code:

Mã: Chọn hết

  1. ' API declerations
  2. Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  3. Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, _
  4.     ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  5.  
  6. ' Constants
  7. Private Const WM_SYSCOMMAND = &H112
  8. Private Const SC_SIZE = &HF000&
  9. Public Const GWL_WNDPROC = (-4) ' This we recognize as the constant for accessing the callback address
  10.  
  11. ' Variables
  12. Public lngOldProc As Long ' This is for preserving the old address of the callback
  13.  
  14. Public Function myWindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  15.  
  16.     ' See if this is a WM_SYSCOMMAND message.
  17.     If Msg = WM_SYSCOMMAND Then
  18.         ' This is a WM_SYSCOMMAND message.
  19.         ' If the command is SC_SIZE, ignore it.
  20.         If (wParam And &HFFF0) = SC_SIZE Then Exit Function
  21.     End If
  22.  
  23.     ' The CallWindowProc API will execute a callback function when given its location.
  24.     ' Here we pass the lngOldProc, the address for the old callback function. We also pass the message and parameters.
  25.     ' So basicly, this just performs what would normally happen.
  26.     myWindowProc = CallWindowProc(lngOldProc, hwnd, Msg, wParam, lParam)
  27.    
  28. End Function

Chúng ta thấy rằng với đoạn code bình thường sẽ có hiện tượng giựt khi cố resize Form. Còn với đoạn code sử dụng Subclassing, do chúng ta chặn ngay khi thông điệp thay đổi kích thước form được gửi tới và hủy bỏ nó, làm cho Form không nhận được thông điệp yêu cầu thay đổi kích thước của mình.
Chúng ta cũng thường thấy kỷ thuật này trong các mã nguồn UserControl (control tự tạo), ta chặn các thông điệp của người dùng tương tác vào UserControl như Click, Double click, Key up, Key down… để phát sinh ra các sự kiện (event) cho người dùng sử dụng.

Callback function?
Đây là những hàm tự động được triệu gọi (bởi hệ thống) cho dù chúng ta không có bất kỳ dòng lệnh nào gọi nó chạy. Ví dụ như hàm myWindowProc ở trên sẽ tự động chạy khi chúng ta subclass.

Sử dụng kỷ thuật Subclassing như thế nào?
Để thực hiện subclass, chúng ta phải sử dụng hằng số GWL_WNDPROC cho hàm SetWindowLong như sau:

Mã: Chọn hết

  1. lngOldProc = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf myWindowProcFunction)

hwnd: handle của cửa sổ (control) cần subclass.
AddressOf: từ khóa, dùng để trỏ tới địa chỉ của hàm.
myWindowProcFunction: tên hàm dùng để thay thế cho process hiện tại. Hàm này phải được đặt trong module và khai báo là Public. Có cú pháp như sau:

Mã: Chọn hết

  1. Public Function myWindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  2.     ' Code xử lý của bạn ở đây.    
  3. End Function

Cấu trúc của hàm này tương ứng với cấu trúc hàm API CallWindowProc:

Mã: Chọn hết

  1. Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

lngOldProc: biến lưu giữ địa chỉ của process cũ. Khi gọi thành công hàm SetWindowLong sẽ tự trả về giá trị process cũ. Do đó nó tương với đoạn code sau:

Mã: Chọn hết

  1. ' Lấy process hiện tại
  2.     lngOldProc = GetWindowLong(hwnd, GWL_WNDPROC)
  3.     ' Trỏ qua process mới
  4.     SetWindowLong hwnd, GWL_WNDPROC, AddressOf myWindowProcFunction

Khi không cần subclass nữa, chúng ta cũng dùng SetWindowLong để trỏ lại process cũ:

Mã: Chọn hết

  1. SetWindowLong hwnd, GWL_WNDPROC, lngOldProc
:>

Hình đại diện của người dùng
NoBi
Quản trị
Quản trị
Bài viết: 957
Ngày tham gia: T.Ba 18/03/2008 1:22 pm
Đến từ: Sài Gòn
Has thanked: 53 time
Been thanked: 66 time
Liên hệ:

Re: SetWindowLong, GetWindowLong và Subclassing

Gửi bàigửi bởi NoBi » T.Tư 04/02/2009 11:27 pm

Bây giờ chúng ta cùng xét 1 ví dụ sau. Bạn copy đoạn code dành cho form và module tương ứng bên dưới vào và F5 chạy thử:
Form code:

Mã: Chọn hết

  1. Private Sub Form_Load()
  2.     ' lngOldProc now holds the old addrss of the callback function.
  3.     ' Subclass
  4.     lngOldProc = SetWindowLong(Me.hwnd, GWL_WNDPROC, AddressOf myWindowProc)
  5.    
  6.     ' 2 dong code ben duoi tuong duong voi dong code tren
  7.     'lngOldProc = GetWindowLong(Me.hwnd, GWL_WNDPROC)
  8.     'SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf myWindowProc
  9. End Sub
  10.  
  11. Private Sub Form_Unload(Cancel As Integer)
  12.     ' Remove Subclass
  13.     SetWindowLong Me.hwnd, GWL_WNDPROC, lngOldProc
  14. End Sub

Module code:

Mã: Chọn hết

  1. ' API declerations
  2. Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  3. Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
  4. Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  5.  
  6. ' Constants
  7. Public Const GWL_WNDPROC = (-4) ' This we recognize as the constant for accessing the callback address
  8.  
  9. ' Variables
  10. Public lngOldProc As Long ' This is for preserving the old address of the callback
  11.  
  12. Public Function myWindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  13.     ' Chua viet gi het
  14. End Function

Bạn thấy gì nào?. Project có chạy nhưng không thấy hiện 1 cái Form nào lên hết. Nguyên nhân là do bạn đã trỏ cái process có nhiệm vụ quản lý cái form mới chạy đó qua hàm xử lý của bạn là myWindowProc rồi. Việc làm này khiến cho Form không nhận được bất kỳ thông điệp xử lý nào (vì trong myWindowProc chưa có đoạn code nào để xử lý), nó như 1 thực thể không có người quản lý, ngay cả bạn đóng nó cũng không được -> kéo theo IDE của Visual Basic cũng treo theo :))). Bạn rút ra được kinh nghiệm gì nào?!.

1. Trước khi chạy 1 ứng dụng có sử dụng Subclassing cần phải lưu project lại vì chỉ cần 1 lỗi nhỏ cũng đủ làm bạn mất hết công sức đã ngồi code.
2. Tuyệt đối không dừng (end) project đang debug lại đột ngột. Nên gở bỏ subclass trước khi thoát để tránh ứng dụng bị scrash.


Bây giờ thì thêm dòng này vào myWindowProc xem sao!?.

Mã: Chọn hết

  1. Public Function myWindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  2.     myWindowProc = CallWindowProc(lngOldProc, hwnd, Msg, wParam, lParam)
  3. End Function


F5 thử xem nào :P. Project chạy ok!. Vậy là bạn đã hiểu được ý nghĩa của đoạn code mới thêm vào rồi phải không?. Đại khái là bạn đã giành quyền kiểm soát cái Form, nhưng bên trong myWindowProc bạn lại bảo nó: “Thôi cái message này giao cho mày xử lý nè ku hđh!”.
Mà trong myWindowProc chúng ta chỉ có mỗi dòng:
myWindowProc = CallWindowProc(lngOldProc, hwnd, Msg, wParam, lParam)
Tức là bất kỳ cái message nào nhận được cũng giao cho process cũ (của hệ điều hành) xử lý hết.

Vậy ý nghĩa của hàm API CallWindowProc: chạy tới process có địa chỉ là lngOldProc nhờ nó xử lý giùm cái thông điệp Msg với các tham số là wParam, lParam của cái cửa sổ có handle là hwnd (Mấy cái giá trị này lấy từ myWindowProc).

Rồi, bây giờ thay module code bằng đoạn code dưới, phần form code vẫn giữ nguyên:
Module code:

Mã: Chọn hết

  1. ' API declerations
  2. Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  3. Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
  4. Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  5.  
  6. ' Constants
  7. Public Const GWL_WNDPROC = (-4) ' This we recognize as the constant for accessing the callback address
  8. Private Const WM_KEYDOWN As Long = &H100
  9.  
  10. ' Variables
  11. Public lngOldProc As Long ' This is for preserving the old address of the callback
  12.  
  13. Public Function myWindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  14.  
  15.     If Msg = WM_KEYDOWN Then MsgBox "WM_KEYDOWN: Msg=" & Msg
  16.  
  17.     myWindowProc = CallWindowProc(lngOldProc, hwnd, Msg, wParam, lParam)
  18.    
  19. End Function

Bạn chạy và nhấn bất kỳ 1 phím nào đó xem. Trong vô vàn window message nhận được, myWindowProc sẽ kiểm tra xem nếu cái nào là WM_KEYDOWN thì sẽ cho xuất hiện hộp thông báo. Thật ra mỗi thông điệp là 1 giá trị số khác nhau được hđh qui định sẳn. Để cho dể nhớ, người ta đặt tên hằng số cho từng giá trị số tương ứng thay vì phải nhớ con số 256 = &H100 (WM_KEYDOWN: nhìn là biết sự kiện nhấn phím rồi :D).

Tiếp tục thay đoạn code trong module bằng:
Module code:

Mã: Chọn hết

  1. ' API declerations
  2. Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  3. Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, _
  4.     ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  5.  
  6. ' Constants
  7. Private Const WM_SYSCOMMAND = &H112
  8. Private Const SC_SIZE = &HF000&
  9. Public Const GWL_WNDPROC = (-4) ' This we recognize as the constant for accessing the callback address
  10.  
  11. ' Variables
  12. Public lngOldProc As Long ' This is for preserving the old address of the callback
  13.  
  14. Public Function myWindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  15.  
  16.     ' See if this is a WM_SYSCOMMAND message.
  17.     If Msg = WM_SYSCOMMAND Then
  18.         ' This is a WM_SYSCOMMAND message.
  19.         ' If the command is SC_SIZE, ignore it.
  20.         If (wParam And &HFFF0) = SC_SIZE Then Exit Function
  21.     End If
  22.  
  23.     ' The CallWindowProc API will execute a callback function when given its location.
  24.     myWindowProc = CallWindowProc(lngOldProc, hwnd, Msg, wParam, lParam)
  25.  
  26. End Function

Như bạn thấy, đoạn code mới này chặn thông điệp WM_SYSCOMMAND và kiểm tra xem trong message WM_SYSCOMMAND được gửi tới này, nếu là yêu cầu resize form (SC_SIZE) thì sẽ thoát khỏi hàm, không chạy dòng lệnh CallWindowProc bên dưới luôn. Vậy có nghĩa là nó không gửi tiếp yêu cầu resize form cho thằng process của hđh xử lý nữa -> thằng Form đang chạy sẽ không bao giờ nhận được thông điệp yêu cầu resize.

Để tránh scrash khi debug project có câu móc (Hook) vào process của hệ thống như Subclassing, người ta hay bỏ qua, không chạy dòng code subclass khi debug, chỉ chạy khi được dịch ra file exe. Bạn có thể tham khảo đoạn code bên dưới:

Mã: Chọn hết

  1. ' In General declerations
  2. Dim InIDE As Boolean
  3.  
  4. Private Function SetIDE() As Boolean
  5.     InIDE = True
  6.     SetIDE = True
  7. End Function
  8.  
  9. Private Sub Form_Load()
  10.     ' To check:
  11.     Debug.Assert SetIDE
  12.    
  13.     If InIDE = False Then
  14.         ' Subclass!
  15.         lngOldProc = SetWindowLong(Me.hwnd, GWL_WNDPROC, AddressOf myWindowProc)
  16.     End If
  17.  
  18. End Sub
  19.  
  20. Private Sub Form_Unload(Cancel As Integer)
  21.     If InIDE = False Then
  22.         ' Remove Subclass
  23.         SetWindowLong Me.hwnd, GWL_WNDPROC, lngOldProc
  24.     End If
  25. End Sub

Với 2 ví dụ nhỏ cơ bản ở trên, tôi hy vọng sẽ là bước đệm cho các bạn yêu thích lập trình application có thể đọc và hiểu các đoạn code có sử dụng kỷ thuật Subclassing này. Cuối cùng việc mở rộng thêm kiến thức còn lại là của các bạn vì chỉ có kinh nghiệm mới có thể nhanh chóng biết được thông điệp nào sẽ được phát sinh khi resize, close ứng dụng, hoặc khi click chuột…
Thân!.
:>

Hình đại diện của người dùng
NoBi
Quản trị
Quản trị
Bài viết: 957
Ngày tham gia: T.Ba 18/03/2008 1:22 pm
Đến từ: Sài Gòn
Has thanked: 53 time
Been thanked: 66 time
Liên hệ:

Re: SetWindowLong, GetWindowLong và Subclassing

Gửi bàigửi bởi NoBi » T.Ba 10/03/2009 11:32 am

Bài viết này mình sưu tầm được, gửi lên cho anh em tham khảo luôn:
Bài viết này sẽ giúp bạn hiểu kỹ thuật subclassing trong VisualBasic. Bạn có thể áp dụng cho các đối tượng khác khi lập trình trong VB


Windows gửi thông điệp là một hằng số tới các form và các control của VB để báo cho chúng biết vị trí chuột ở đâu, khi nào thì cần vẽ lại, phím nào đang được nhấn và nhiều thông điệp khác. Kỹ thuật subclassing là để xử lý chặn những thông điệp này trước khi chúng đến được các form và control. Bằng cách chặn các thông điệp này và xử lý ''vài thứ'' trước khi chúng đến đích, chúng ta có thể có các tính năng riêng (như tự vẽ lại các control theo ý riêng).
Subclassing là một kỹ thuật tinh vi, chỉ cần một lỗi nhỏ (ví dụ như : do bạn giải phóng tài nguyên không tốt dẫn đến việc thất thoát tài nguyên của hệ thống) là có thể dẫn đến việc hệ thống của bạn bị thiếu tài nguyên làm cho hệ thống hoạt động không còn tốt nữa (chậm đi), nặng hơn là VB bị shut down, thậm chí treo máy. Tuy nhiên nói điều này là để bạn ý thức được vấn đề chứ bạn cũng không nên quá lo ngại về nó. Và thêm 1 chú ý là bạn cũng không nên bấm nút stop của VB khi chương trình đang chạy mà bạn nên đóng form 1 cách thông thường (bấm nút close) để thực hiện tốt việc giải phóng tài nguyên.

Subclassing là một kỹ thuật tinh vi, chỉ cần một lỗi nhỏ (ví dụ như : do bạn giải phóng tài nguyên không tốt dẫn đến việc thất thoát tài nguyên của hệ thống) là có thể dẫn đến việc hệ thống của bạn bị thiếu tài nguyên làm cho hệ thống hoạt động không còn tốt nữa (chậm đi), nặng hơn là VB bị shut down, thậm chí treo máy. Tuy nhiên nói điều này là để bạn ý thức được vấn đề chứ bạn cũng không nên quá lo ngại về nó. Và thêm 1 chú ý là bạn cũng không nên bấm nút stop của VB khi chương trình đang chạy mà bạn nên đóng form 1 cách thông thường (bấm nút close) để thực hiện tốt việc giải phóng tài nguyên.

Subclassing the Main Window:

Chúng ta bắt đâu thực hiện kỹ thuật subclassing bằng cách bạn mở 1 project mới và thêm 1 module vào project (project/add module/open). Bây giờ bạn đã có Form1 và Module1 trong project.
Bạn mở Module1 ra và copy, paste đoạn code sau vào :

Mã: Chọn hết

  1. Public Const GWL_WNDPROC = (-4)
  2. Public oldWindowProc As Long
  3.  
  4. Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _
  5. ByVal hwnd As Long, _
  6. ByVal nIndex As Long, _
  7. ByVal dwNewLong As Long) As Long


Đây là một hàm API của Windows cho phép bạn thay đổi thuộc tính của 1 cửa sổ (hay control - từ bây giờ chúng ta coi như control cũng là một window), trong trường hợp của chúng ta là thay đổi hàm WinProc (hàm Winproc là hàm mà các window dùng để xử lý các thông điệp do hệ thống (hệ điều hành Windows) gửi đến).

hwnd - tham số này có kiểu là long integer dùng để xác định 1 cửa sổ (form) hay 1 control (bạn có thể coi nó như bảng số xe dùng đê xác định tính duy nhất của 1 xe vậy).

nIndex - tham số này cũng có kiểu là long integer dùng để xác định ''cần thay đổi cái gì'' trong hàm SetWindowLong nói trên (bạn có thể tham khảo trong bộ MSDN), trong trường hợp của chúng ta nIndex có giá trị là GWL_WNDPROC (vì chúng ta cần xử lý hàm WinProc mà).

dwNewLong - hàm này có kiểu long integer dùng để chỉ ra địa chỉ của thủ tục mới mà chúng ta cần xử lý.

Hàm WinProc mới phải có các tham số giống hệt các tham số của hàm WinProc bị thay thế. Bạn cũng phải chú ý là bạn phải gửi trả các thông điệp mà bạn không xử lý cho hàm WinProc mặc định xử lý. Bạn tiếp tục copy và dán đoạn mã sau vào Module1 :

Mã: Chọn hết

  1. Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" ( _
  2. ByVal lpPrevWndFunc As Long, _
  3. ByVal hwnd As Long, _
  4. ByVal Msg As Long, _
  5. ByVal wParam As Long, _
  6. ByVal lParam As Long) As Long
  7.  
  8. Public Function NewWindowProc( _
  9. ByVal hWnd As Long, _
  10. ByVal uMsg As Long, _
  11. ByVal wParam As Long, _
  12. ByVal lParam As Long) As Long
  13.  
  14. Debug.Print "&H" & Hex(uMsg), wParam, lParam
  15. NewWindowProc = CallWindowProc(oldWindowProc, hWnd, uMsg, wParam, lParam)
  16. End Function


CallWindowProc dùng để gọi hàm WinProc mặc định ra xử lý, hàm NewWindowProc là hàm thay thế cho hàm WinProc. Hàm NewWindowProc không làm bất cứ việc gì ngoại trừ việc in ra cửa sổ Debug xem thông điệp gì được gửi đến cho cửa sổ này (cửa sổ bị subclassing). Hàm NewWindowProc sau đó gọi hàm WinProc mặc định để xử lý thông điệp 1 cách bình thường (biến oldWindowProc dùng để lưu địa chỉ hàm WinProc mặc định).Tham số mà hệ thống gửi cho hàm NewWindowProc là : hWnd - handle của cửa sổ sẽ nhận thông điệp; uMsg - thông điệp được gửi; và 2 tham số còn lại (wParam và lParam) mang thông tin của thông điệp, phụ thuộc vào thông điệp được gửi.

Bây giờ bạn có thể chạy project được, nhưng chưa có chuyện gì xảy ra cả, cửa sổ (form) của bạn chưa bị subclass. Một lần nữa xin nhắc lại là bạn không nên bấm vào nút stop để dừng chương trình và bạn cũng nên lưu project lại trước khi chạy.
Để thực hiện subclass cửa sổ (form) của bạn, bạn double vào form và copy, paste đoạn code sau vào :

Mã: Chọn hết

  1. Private Sub Form_Load()
  2. 'Subclass the window
  3. oldWindowProc = SetWindowLong(Me.hWnd, GWL_WNDPROC, AddressOf NewWindowProc)
  4. End Sub
  5.  
  6. Private Sub Form_Unload(Cancel As Integer)
  7. 'Unsubclass (return the original window process)
  8. SetWindowLong Me.hWnd, GWL_WNDPROC, oldWindowProc
  9. End Sub


Bây giờ thì ok, form của bạn đã bị subclass ! Bạn thử chạy project và xem điều gì xảy ra ? Cửa sổ Debug của bạn sẽ tràn ngập những thông tin về thông điệp mà hệ thống đã gửi cho form của bạn, bạn thử di chuyển chuột, thay đổi kích thước form ... mà xem. (Hàm AddressOf dùng để lấy địa chỉ của 1 hàm).
:>

Hình đại diện của người dùng
truongphu
VIP
VIP
Bài viết: 4763
Ngày tham gia: CN 04/11/2007 10:57 am
Đến từ: Cam Đức, Khánh hòa
Has thanked: 14 time
Been thanked: 518 time

Re: SetWindowLong, GetWindowLong và Subclassing

Gửi bàigửi bởi truongphu » T.Tư 03/02/2010 11:03 pm

peterdrew đã viết:cái &HFFF0 kia có ý nghĩa gì?


1- Đấy là hằng GWL_STYLE (-16)
2- với chuỗi 4 số 0 khi dùng phép toán AND sẽ lấy các thông điệp từ F000 đến F00F
o0o--truongphu--o0o

.........
Ghé thăm:
Chuyện Linh Tinh

Hình đại diện của người dùng
NoBi
Quản trị
Quản trị
Bài viết: 957
Ngày tham gia: T.Ba 18/03/2008 1:22 pm
Đến từ: Sài Gòn
Has thanked: 53 time
Been thanked: 66 time
Liên hệ:

Re: SetWindowLong, GetWindowLong và Subclassing

Gửi bàigửi bởi NoBi » T.Sáu 05/02/2010 11:26 am

Các tham số wParam dành cho WM_SYSCOMMAND bao gồm:
  1. SC_CLOSE
  2.     Close the window.
  3. SC_CONTEXTHELP
  4.     Display a context-sensitive help window.
  5. SC_MAXIMIZE
  6.     Maximize the window.
  7. SC_MINIMIZE
  8.     Minimize the window.
  9. SC_MOVE
  10.     Move the window.
  11. SC_RESTORE
  12.     Restore the window.
  13. SC_SIZE
  14.     Size the window.

Còn lParam dùng để chỉ tọa độ con trỏ tại thời điểm đó (The low-order word specifies the horizontal position of the cursor, The high-order word specifies the vertical position of the cursor).

Riêng với wParam, số cuối cùng (4 bit) được dùng riêng bởi hệ thống và chúng ta không quan tâm đến con số này.

Chúng ta biết 1 số mà AND với số 0 thì bằng 0. Do đó bất kỳ số nào AND với &HFFF0 cũng sẽ được cắt bỏ số cuối cho phù hợp.

Tham khảo thêm:
http://www.xs4all.nl/~rjg70/vbapi/ref/w ... mmand.html
http://msdn.microsoft.com/en-us/library/ms646360(VS.85).aspx
:>

Hình đại diện của người dùng
truongphu
VIP
VIP
Bài viết: 4763
Ngày tham gia: CN 04/11/2007 10:57 am
Đến từ: Cam Đức, Khánh hòa
Has thanked: 14 time
Been thanked: 518 time

Re: SetWindowLong, GetWindowLong và Subclassing

Gửi bàigửi bởi truongphu » T.Sáu 05/02/2010 11:58 am

NoBi đã viết:Chúng ta biết 1 số mà AND với số 0 thì bằng 0. Do đó bất kỳ số nào AND với &HFFF0 cũng sẽ được cắt bỏ số cuối cho phù hợp.


truongphu đã viết:2- với chuỗi 4 số 0 khi dùng phép toán AND sẽ lấy các thông điệp từ F000 đến F00F


* Lưu ý &HFFF0 có số nhị phân là 1111 1111 1111 0000
nếu dùng phép AND để lọc ra kết quả &HF000, ta chỉ lọc với tất cả các số có số nhị phân theo cấu trúc:
xxxx hoặc
1111 0000 0000 xxxx

và viết theo hệ 16 là: &HF00x

mà các hằng thuộc WM_SYSCOMMAND là:

Mã: Chọn hết

WM_SYSCOMMAND = &H112
Public Enum WM_SYSCOMMAND As Integer
    SC_CLOSE = &HF060
    SC_CONTEXTHELP = &HF180
    SC_DEFAULT = &HF160
    SC_HOTKEY = &HF150
    SC_HSCROLL = &HF080
    SC_KEYMENU = &HF100
    SC_MAXIMIZE = &HF030
    SC_MINIMIZE = &HF020
    SC_MONITORPOWER = &HF170
    SC_MOUSEMENU = &HF090
    SC_MOVE = &HF010
    SC_NEXTWINDOW = &HF040
    SC_PREVWINDOW = &HF050
    SC_RESTORE = &HF120
    SC_SCREENSAVE = &HF140
    SC_SIZE = &HF000
    SC_TASKLIST = &HF130
    SC_VSCROLL = &HF070
End Enum


Trong bảng trên, chỉ có SC_SIZE = &HF000 là hoàn toàn hợp điều kiện nêu trên
do đấy, tôi vẫn còn phân vân:
thay vì:
If wParam = SC_SIZE Then Exit Function
TẠI SAO PHẢI VIẾT:
If (wParam And &HFFF0) = SC_SIZE Then Exit Function

Trước đây, trả lời nhanh theo logic, chưa nắm các hằng... tôi viết :
dùng phép toán AND sẽ lấy các thông điệp từ F000 đến F00F

bây giờ xem lại, trong chuỗi F000 đến F00F chỉ có hằng duy nhất SC_SIZE

----
Túm lại, giải thích thông suốt sự khác nhau của:
If wParam = SC_SIZE Then Exit Function
If (wParam And &HFFF0) = SC_SIZE Then Exit Function
là OK
o0o--truongphu--o0o

.........
Ghé thăm:
Chuyện Linh Tinh

Hình đại diện của người dùng
NoBi
Quản trị
Quản trị
Bài viết: 957
Ngày tham gia: T.Ba 18/03/2008 1:22 pm
Đến từ: Sài Gòn
Has thanked: 53 time
Been thanked: 66 time
Liên hệ:

Re: SetWindowLong, GetWindowLong và Subclassing

Gửi bàigửi bởi NoBi » T.Sáu 05/02/2010 11:03 pm

Trời, bài viết trên của cháu đã giải thích rỏ ràng rồi còn gì. Chú có thấy tất cả giá trị của các hằng số SC_ đều là số 0 ở cuối kg?.
Thực sự khi resize form thì ta nhận được giá trị wParam = &HF002. Như bài viết trên cháu có nói, con số cuối cùng (là số 2) là con số "nội bộ" dành riêng cho hệ thống (xử lý gì đó mình không biết). Mình chỉ cần quan tâm giá trị 3 con số đầu. Như vậy:
&HF002
AND
&HFFF0
-------
&HF000

Con số &HFFF0 chỉ là để giữ lại giá trị 3 con số đầu của số AND với nó, còn số cuối luôn bằng 0. Chứ không phải đó là giá trị hằng GWL_STYLE.
:>


Quay về “[VB] Bài viết hướng dẫn”

Đang trực tuyến

Đang xem chuyên mục này: Không có thành viên nào trực tuyến.0 khách