#Class & cơ bản về lập trình hướng đối tượng trong VBA

Chủ đề này có hữu ích với bạn ?


  • Số thành viên bình chọn
    51

thaipv

Mod
Thành viên BQT
Chào các bạn.

Hôm nay tôi xin giới thiệu với các bạn về 1 kỹ thuật lập trình lớp (class) trong VBA. Đây là kỹ thuật lập trình hướng đối tượng, tuy không mới nhưng sẽ rất thú vị. Nếu nắm vững kỹ thuật này, các bạn có thể code ra những chương trình hết sức mềm dẻo mà nếu viết trên module sẽ rất khó khăn (hoặc thậm chí không thể thực hiện được).

Đầu tiên, các bạn tìm hiểu kỹ thuật lập trình hướng đối tượng qua liên kết sau :
Và tại đây

Bài 0 : Viết class đầu tiên

Bây giờ, chúng ta sẽ xây dựng 1 lớp Mèo. Lớp này có những thuộc tính sau : tên, màu lông, giới tính

Bước đầu tiên thường sẽ là bước khó khăn nhất. Trong lập trình, lúc đầu chúng ta không hiểu gì nhưng buộc phải tự công nhận một vài khái niệm (Đây có thể gọi là tiên đề trong lập trình - Xem ghi chú 1). Vì vậy bài này các bạn thực hiện theo những chỉ dẫn của tôi, cứ code nhiều các bạn sẽ tự 'ngộ' ra chân lý nhé.

+ Trên cửa sổ Microsoft Visual Basic for Application, vào Insert \ Class Module
Bạn cần đăng nhập để thấy đính kèm

+ Trên cửa sổ Properties của class vừa được tạo, đổi tên thành cCat
+ Xây dựng thuộc tính : tên
Vì tên mèo là chữ nên thuộc tính này kiểu String. Trên cửa sổ soạn thảo code, các bạn ghi :
PHP:
Public strName As String
+ Xây dựng thuộc tính : màu lông
Màu lông có thể là tên màu (kiểu String) hoặc số của màu (kiểu Long). Để đơn giản, tôi chọn kiểu chuỗi. Trên cửa sổ soạn thảo code, các bạn ghi tiếp :
PHP:
Public strColor As String
+ Xây dựng thuộc tính : giới tính
Ở đây tôi giả sử mèo chỉ có 2 giới tính là đực và cái (không có giới tính thứ 3 ở mèo nhé) nên thuộc tính này có kiểu Đúng/Sai. Trên cửa sổ soạn thảo code, các bạn ghi tiếp nữa :
PHP:
Public blnSex As Boolean
+ Như vậy, toàn bộ code của lớp Mèo chúng ta vừa xây dựng như sau :
PHP:
Option Explicit

Public strName  As String
Public strColor As String
Public blnSex   As Boolean
Thế là chúng ta vừa viết xong class đầu tiên. Tôi kết thúc bài này tại đây. Chúc vui và hẹn sớm gặp lại.

Ghi chú 1 : Tiên đề trong lập trình.
'Tiên đề' này làm tôi nhớ lại 'Tiên đề Ơ cờ lít' trong hình học : Cho đường thẳng a và một điểm M nằm ngoài đường đường thẳng a. Có 1 và chỉ 1 đường thẳng đi qua M và song song với đường thẳng a.

Các bạn không thể chứng minh (chưa có cơ sở nào mà chứng minh) nhưng phải tự công nhận nó (đây là nền móng của hình học).
Trong lập trình cũng vậy, lúc đầu những khái niệm rất mông lung, nhưng viết code nhiều các bạn sẽ tự hiểu.
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Chào các bạn,

Ở bài trước, chúng ta đã tìm hiểu về Lập trình hướng đối tượng và viết class đầu tiên là lớp Mèo gồm 3 thuộc tính cơ bản là : tên, màu lông và giới tính.

Hôm nay chúng ta sẽ tìm hiểu Bài 1 : Một số khái niện cơ bản về lớp nhé :

Lớp (Class) là phương pháp lập trình nâng cao được Microsoft tích hợp sẵn trong VBA cho phép lập trình viên tạo ra những khuân mẫu hoàn toàn mới và/hoặc phát triển thêm những tính năng mới (phương thức, thuộc tính (lập trình hướng đối tượng), sự kiện (lập trình hướng sự kiện)) cho những khuân mẫu (và/hoặc đối tượng) sẵn có.

Các tính chất của lập trình hướng đối tượng các bạn xem trên wkipedia bài trước ( ).

Đối tượng (Object) : Ý tưởng của phương pháp lập trình hướng đối tượng là việc trừu tượng hóa các đối tượng thành các lớp thông qua việc quan sát các thực thể ở thế giới thực. Ta có thể coi đối tượng là 1 bản sao của lớp ở thế giới thực, hay đối tượng là 1 thể hiện cụ thể của lớp.

Ví dụ 1 : Bản thiết kế nhà là lớp, ngôi nhà được xây dựng dựa trên bản thiết kế là đối tượng.
Ví dụ 2 : Bản thiết kế nhà là lớp, ngôi nhà A được xây dựng trên bản thiết kế là đối tượng A, ngôi nhà B được xây dựng trên bản thiết kế là đối tượng B. Hai ngôi nhà A và B là 2 thực thể (đối tượng) khác nhau của cùng 1 bản thiết kế (lớp).
Ví dụ 3 : Class đầu tiên chúng ta viết là 1 lớp (lớp Mèo), chưa phải là 1 đối tượng.

Trong tài liệu này, khi nói đến lớp hàm ý xây dựng lớp mới và/hoặc xây dựng tính năng mới từ đối tượng sẵn có (workbook, worksheet, range…), không bao gồm đối tượng được thực thể hóa từ lớp thông qua tạo mới (từ khóa New).

Tài liệu này cũng hàm ý đối tượng là tất cả các đối tượng sẵn có (đã thực thể hóa) và chưa thực thể hóa (là đối tượng nhưng chưa thực thể hóa, tức là lớp nhưng có ý định và sẽ dựng đối tượng), nhưng không bao gồm lớp nguyên mẫu.

=> Những kiến thức về lớp hoàn toàn có thể áp dụng cho 1 đối tượng cụ thể trong VBA (workbook, worksheet, range, userform, control…). Sự khác biệt ở đây là lớp là khuân mẫu, vì vậy muốn sử dụng ta phải dựng nó thành 1 thực thể (thông qua từ khóa New). Ngược lại, đối tượng bản thân nó đã là 1 thực thể nên ta không cần dựng mới mà chỉ cần trỏ (từ khóa Set) đến đúng vị trí của nó mà thôi.

Phương thức (Method) : Phương thức của một lớp thường được dùng để mô tả các hành vi (thao tác) của lớp.
Theo phương pháp lập trình thủ tục (còn gọi lập trình cấu trúc), đây là những chương trình con bao gồm hàm (function) và thủ tục (sub), còn trong lớp ta gọi chung là phương thức (xem thêm Ghi chú 1)

Ví dụ 4 : Đối tượng thuộc lớp xe có các phương thức là đi thẳng, rẽ trái, rẽ phải, đi lùi, …
Ví dụ 5 : Đối tượng thuộc lớp Mèo có các phương thức là Ăn, Ngủ, Ị, Kêu, Rình, Vồ... (trong bài trước chúng ta chưa xây dựng phương thức cho lớp Mèo)

Thuộc tính (Property) : Trong thế giới thực, thuộc tính – giống như tên gọi của nó là những tính chất, đặc điểm của đối tượng. Ví dụ, lớp xe có các thuộc tính là màu sơn (đen, trắng, vàng,…), kiểu dáng (thể thao, hạng sang,…), mẫu xe (phổ thông, bán tải, SUV, sedan,…), nhà sản xuất (Toyota, Honda, Hyundai, Audi,…), …

Trong lập trình hướng đối tượng, thuộc tính của lớp bao gồm các biến, các hằng, hay tham số nội tại của lớp đó. Vai trò quan trọng nhất của các thuộc tính là các biến vì chúng sẽ có thể bị thay đổi trong suốt quá trình hoạt động của một đối tượng. Các thuộc tính có thể được xác định kiểu và kiểu của chúng có thể là các kiểu dữ liệu cổ điển hay đó là một lớp đã định nghĩa từ trước. Như đã ghi, khi một lớp đã được thực thể hoá thành đối tượng cụ thể thì tập hợp các giá trị của các biến nội tại làm thành trạng thái của đối tượng.

Sự kiện (Event) : Một trong những thành phần quan trọng bậc nhất của một đối tượng là sự kiện. Sự kiện là một sư kích thích (thông điệp) được gửi từ đối tượng này sang đối tượng khác. Đây là chủ đề hay nhất của lớp / đối tượng trong VBA.

Khi sử dụng đối tượng nếu lập trình viên bắt lấy những thông điệp mà đối tượng đó gửi ra ngoài tại 1 thời điểm thì ta gọi là bắt sự kiện. Ngược lại, trong khi xây dựng lớp / đối tượng tại 1 thời điểm mà lập trình viên muốn thông báo (gửi thông điệp) ra bên ngoài lớp thì ta gọi là tạo sự kiện. Có rất nhiều tài liệu đã viết về bắt sự kiện, vì vậy, tài liệu này sẽ lướt qua và sẽ chú trọng hơn vào lập trình hướng sự kiện (tạo các sự kiện cho lớp / đối tượng).

(Lập trình hướng sự kiện là phương pháp tạo ra các kịch bản cho các tình huống (sự kiện) được biết trước sẽ xảy ra trên các thành phần / đối tượng của chương trình.)

*Mẹo : Để đơn giản hóa những khái niệm trên, chúng ta có thể coi :
+ Lớp, đối tượng ≈ Danh từ trong tiếng Việt
+ Phương thức ≈ Động từ
+ Thuộc tính ≈ Tính từ
+ Sự kiện ≈ Trạng từ (chỉ thời gian)

Ghi chú 1 : Thủ tục (Sub) và Hàm (Function) trong lập trình hướng thủ tục (viết trên Module trong VBA)
Tình huống 1 : Giá trị của hàm trả về thông qua tên hàm (Xem ví dụ 6 bên dưới)
Nếu bạn thay tất cả các Sub của 1 module VBA thành Function thì nhiều khả năng chương trình của bạn vẫn hoạt động tốt. Ngược lại, nếu thay tất cả các Function thành Sub thì nhiều khả năng chương tình của bạn sẽ bị lỗi.

Từ Sub (thủ tục) là viết tắt của từ nào ? (Tôi cũng không biết nữa, chỉ biết rằng khi dùng Sub thì sẽ không có giá trị trả về). Đến đây, có lẽ nhiều bạn cũng đã nhớ về bài học đầu tiên khi lập trình VBA. Sub là gì ? Không biết, nhưng cứ viết 1 cái Sub đầu tiên rồi tính sau. Đây chính là 'tiên đề trong lập trình' - những khái niệm mơ hồ chưa biết là gì nhưng phải tự công nhận trước (Xem lại 'tiên đề' này ở bài 01 nhé).

Tình huống 2 : Trả về giá trị của hàm (function) trong biến. Đây là cách lập trình khá an toàn (tại sao lại an toàn hơn tình huống 1, các bạn tự tìm hiểu) (các hàm API của Microsoft thường viết theo cách này)
Ví dụ 6 : Hàm trả về giá trị bằng tên hàm :
PHP:
Function f (Byref x as Long) as Long
    f = x + 1 '<- Giá trị trả về là tên hàm f
End Function
Ví dụ 7 : Thủ tục trả về giá trị thông qua biến (ở đây là biến y)
PHP:
Sub f (Byref x as Long, Byref y as Long)
    y = x + 1 '<- Giá trị trả về là biến y
End Sub
Ví dụ 8 : Hàm trả về giá trị thông qua biến (ở đây là biến y). Giá trị trả về thông báo lỗi (không phải mục đích chính) :
PHP:
Function f (Byref x as Long, Byref y as Long) as Boolean
    y = x + 1 '<- Giá trị trả về là biến y
    f =(0 = Err.Number) '<- Hàm trả về giá trị True nếu không lỗi, False nếu có lỗi xảy ra
End Function
Biến thực sự của hàm trên là biến x, biến y là giá trị trả về của hàm (Hàm này cũng có thể chỉ cần 1 biến x vừa là giá trị truyền vào và giá trị trả về).

Kết luận : Thủ tục (Sub) và Hàm (Function) chỉ có ý nghĩa tương đối. Thủ tục cũng có thể trả về giá trị (thông qua biến)(chức năng giống hàm) và hàm cũng không nhất thiết phải trả về giá trị (thế thì khác gì thủ tục đâu ?).
=> Lớp (class) gom chung lại gọi là phương thức là hợp lý.

Link tham khảo :
1.
2. Giáo trình Lập trình hướng sự kiện - Viện Đại học mở Hà Nội
(Hết bài 1)
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Tiếp tục chủ đề, hôm nay tôi trình bày một số phương pháp khai báo và sử dụng lớp, đúng hơn là Bài 2 : Dựng đối tượng từ lớp / đối tượng :

Bài đầu tiên, chúng ta đã xây dựng được 1 lớp Mèo có 3 thuộc tính là : tên, màu lông, giới tính (đây có lẽ là mèo nhồi bông vì chúng chưa có hoạt động nào cả - những phương thức của lớp)
Toàn bộ code của lớp mèo này như sau :
PHP:
Option Explicit

Public strName  As String
Public strColor As String
Public blnSex   As Boolean
Tình huống : Bạn là bán hàng cho 1 cửa hàng thú nhồi bông và cửa hàng này có những con mèo rất đáng yêu. Bạn đặt tên cho mỗi con mèo và ghi chú 1 số thông tin này (bao gồm tên, màu lông và giới tính) phòng khi có khách hàng muốn bạn tư vấn về chúng.

Trong trường hợp này, bạn có thể sử dụng lớp mèo ở trên để lưu trữ thông tin những con mèo trong cửa hàng của bạn. Mỗi đối tượng mèo được định danh (ID) là vị trí trong kệ hàng. Ví dụ : clsCat11 (con mèo ở vị trí 1 trong kệ số 1), tương tự clsCat23 (con mèo ở vị trí số 3 trong kệ số 2)....

Sau đây chúng ta sẽ nghiên cứu cách lưu trữ thông tin những con mèo trong cửa hàng của bạn bằng cách sử dụng lớp trong VBA.
Nhắc lại, lớp chỉ là 1 khuân mẫu được trừu tượng hóa của đối tượng. Chính vì vậy, muốn sử dụng lớp (những con mèo nhồi bông được trừu tượng hóa (tính trừu tượng của lớp) chỉ có 3 thuộc tính, thực tế chúng có nhiều hơn), ta phải khai báo và dựng nó thành 1 thực thể (những con mèo trong của hàng).

Dưới đây là 1 số cách khai báo và dựng đối tượng như sau :
Cách 1 : Khai báo tường minh (Khai báo trước, cấp phát vùng nhớ sau)
PHP:
Dim clsCat11 As cCat
Set clsCat11 = New cCat
Cách 2 : Khai báo tắt, sử dụng ngay (Khai báo và cấp phát vùng nhớ cùng lúc)
PHP:
Dim clsCat11 As New cCat
Lưu ý : Không nên sử dụng cách này trong VBA

Cách 3 : Sử dung With New ... End With
PHP:
With New cCat
    .strName = "Tom"
    .strColor = "Black"
    .blnSex = True
End With
(Cần lưu ý khi sử dụng cách này)

Cách 4 : Sử dụng Type
PHP:
Public Type tCat
     This As New cCat
End Type
(Cần lưu ý khi sử dụng cách này)

Cách 5 : Sử dụng Hàm / Thủ tục
Cách này cũng tuơng tự các cách trên, các bạn có thể tham khảo trên StackOverFlow :

Cách 6 : Khai báo 1 đường, dựng đối tượng 1 nẻo
PHP:
Dim clsCat11 As iAnimal
Set clsCat11 = New cCat
Cách này ứng dụng trong lớp giao diện (interface class), tôi sẽ giới thiệu với các bạn sau.

Ghi chú 1 : Sử dụng đối tượng với sự kiện của nó
PHP:
Dim WithEvents clsCat11 As cCat
Đây chính là bắt sự kiện của đối tượng, tôi sẽ giới thiệu sau.

Ghi chú 2 : Ta cũng có thể xây dựng đối tượng riêng trong VBA (dùng luôn được giống như worksheet, range, ... ) trong VBA mà không cần phải dựng đối tượng (dùng từ khóa New). Tôi sẽ giới thiệu trong bài 'Thuộc tính của lớp' sau.

Đến đây tôi đã giới thiệu với các bạn 1 nửa chặng đường việc Dựng & sử dụng đối tượng từ lớp. Tôi sẽ sớm quay lại và giới thiệu tiếp phần sau là 'Cơ bản về cách sử dụng đối tượng'.
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Bài 3 : Cơ bản về cách sử dụng đối tượng

Bài này, chúng ta sẽ tìm hiểu cơ bản về cách sử dụng đối tượng :
1. Để gọi các thuộc tính của đối tượng, ta sử dụng cú pháp :
Mã:
<Tên đối tượng>.<Tên thuộc tính>
2. Để gán các thuộc tính của đối tượng, ta sử dụng cú pháp :
Mã:
<Tên đối tượng>.<Tên thuộc tính>[Danh sách tham số nếu có]
3. Để gọi các phương thức của đối tượng, ta sử dụng cú pháp :
Mã:
<Tên đối tượng>.<Tên phương thức>[Danh sách tham số nếu có]
4. Sử dụng Sự kiện của đối tượng (giới thiệu sau)

Ví dụ 1 : Gán các thuộc tính của Mèo vào đối tượng cCat11
PHP:
clsCat11.strName = "Tom"
clsCat11.strColor = "Xanh den"
clsCat11.blnSex = True 'Mèo đực
Ví dụ 2 : Bà chủ cửa hàng hỏi bạn thông tin con mèo ở vị trí số 1 trên kệ 1.
PHP:
Dim strCatName As String
Dim strCatColor As String
Dim blnCatSex As Boolean

strCatName = clsCat11.strName
strCatColor = clsCat11.strColor
blnCatSex = clsCat11.blnSex
Như vậy, những thuộc tính của mèo được gán vào 3 biến tương ứng là strCatName, strCatColor và blnCatSex.

Qua 2 ví dụ trên, tôi đã giới thiệu cho các bạn về cách gán và gọi thuộc tính của đối tượng. Đến đây, để đơn giản, các bạn có thể coi lớp đối tượng giống như 1 kiểu dữ liệu có cấu trúc (Type) trong VBA.

Ví dụ 3 : Chúng ta cùng làm 1 bài thực hành nhỏ sau. Ghi vào ô A1 (tại sheet hiện hành) dòng chữ "Hello world" và chọn ô A1 là ô được chọn.
Trên module mới (hoặc 1 module có sẵn), bạn chép đoạn code sau và chạy thử. (Lưu ý, bạn nên chạy code vào 1 file mới để tránh mất dữ liệu)
PHP:
Public Sub ActivateRangeA1()
    '// Khai báo biến đối tượng range
    Dim rngTarget As Range

    '// Dựng đối tượng. Vì đây là đối tượng dựng sẵn nên không cần từ khóa New (Xem lại bài 2) _
    '// mà chỉ cần trỏ đến vùng nhớ của nó (xem lại sự khác nhau giữa đối tượng và lớp trong mục định nghĩa về Đối tượng ở bài 1)
    Set rngTarget = Range("A1")

    '// Gán giá trị đối tượng là "Hello world" 'GÁN GIÁ TRỊ (THUỘC TÍNH) CỦA ĐỐI TƯỢNG (Mục 2)
    rngTarget.Value = "Hello world"

    '// Chọn đối tượng là ô được chọn 'GỌI PHƯƠNG THỨC CỦA ĐỐI TƯỢNG (Mục 3)
    rngTarget.Select

    '// Giải phóng đối tượng và kết thúc chương trình
    Set rngTarget = Nothing
End Sub
*Lưu ý quan trọng : Sau khi sử dụng đối tượng, ta cần giải phóng bộ nhớ bị chiếm giữ bởi đối tượng. Việc này là cần thiết, không những giải phóng tài nguyên hệ thống mà còn tạo thói quen lập trình tốt. Câu lệnh hủy đối tượng thường được đặt ở phần cuối của hàm / thủ tục...
PHP:
Set <Tên đối tượng> = Nothing
Trong nhiều trường hợp, khi code chúng ta thường khai báo tập trung tại đầu mỗi hàm / thủ tục ... để tiện cho việc quản lý. Nếu đối tượng không chắc chắn được dựng, khi giải phóng đối tượng, chúng ta dùng câu lệnh sau :
PHP:
If Not <Tên đối tượng> Is Nothing Then Set <Tên đối tượng> = Nothing
(Hết bài 3)
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Bài 4 : Thuộc tính
Ở bài 0, chúng ta đã xây dựng được lớp Mèo với 3 thuộc tính là Tên (strName), Màu lông (strColor) và Giới tính (blnSex) bằng cách khai báo 3 biến có tầm vực Công khai (Public) trong lớp. Bài này, tôi sẽ giới thiệu cho các bạn cách xây dựng thuộc tính của lớp theo cách chuẩn tắc (chính thống).

Mục tiêu của bài này là giải thích cho các bạn :
+ Hiểu và xây dựng các thuộc tính cho lớp.
+ Hiểu được tính ưu việt của lớp so với kiểu dữ liệu có cấu trúc (Type)

Nhắc lại, 1 trong những tính chất của lớp là tính đóng gói. Tính chất này không cho phép người sử dụng các đối tượng thay đổi trạng thái nội tại của một đối tượng. Chỉ có các phương thức nội tại của đối tượng cho phép thay đổi trạng thái của nó. Việc cho phép môi trường bên ngoài tác động lên các dữ liệu nội tại của một đối tượng theo cách nào là hoàn toàn tùy thuộc vào người viết mã. Đây là tính chất đảm bảo sự toàn vẹn của đối tượng (Wikipedia).

Một cách đơn giản, 1 thuộc tính của lớp có thể là chỉ đọc (read only), chỉ ghi (write only) hoặc đọc và ghi (read write).

Ví dụ về thuộc tính chỉ đọc như thuộc tính OldLeft, OldRight, OldHeight, OldWidth của Frame trong UserForm. Nếu thuộc tính cho phép thay đổi giá trị (ghi - write) thì rõ ràng nó không còn là chính nó nữa !

Ví dụ về thuộc tính chỉ ghi (ít gặp) : Đếm số lần sử dụng khi người dùng mở tệp, nếu vượt qua giới hạn thì thông báo cho người dùng đã hết hạn sử dụng hoặc phải nâng cấp chương trình.

Bây giờ chúng ta sẽ viết lại lớp Mèo theo cách chính thống.
+ Đầu tiên chúng ta khai báo 3 biến trung gian trong lớp như sau :
PHP:
Option Explicit

Private strName_  As String
Private strColor_ As String
Private blnSex_   As Boolean
Các biến này (có thêm dấu gạch dưới_ để không bị trùng với các thuộc tính) bây giờ có tầm vực riêng tư (Private) nên ta không thể ghi hoặc đọc giá trị của chúng như ở bài 0 nữa.

+ Viết thuộc tính Tên (strName). Thuộc tính này cho phép người dùng ghi và đọc.
- Để ghi giá trị vào thuộc tính, ta viết code như sau :
PHP:
Public Property Let strName(ByRef strValue As String)
    strName_ = strValue
End Property
Trong đó strValue là giá trị bên ngoài truyền vào, strName_ là biến tạm (môi trường bên ngoài lớp không thể truy cập biến này) ghi giá trị của thuộc tính này. Khi gán giá trị cho thuộc tính Tên, ta gán biến tạm strName_ là giá trị của biến truyền vào strValue

- Để đọc giá trị của thuộc tính, ta viết code :
PHP:
Public Property Get strName() As String
    strName = stName_
End Property
+ Viết thuộc tính Màu lông (strName) và Giới tính (blnSex). Tương tự như trên.

+ Như vậy, toàn bộ code của lớp Mèo chúng ta đã viết lại như sau :
PHP:
Option Explicit

'// Khai báo các biến trung gian
Private strName_    As String
Private strColor_   As String
Private blnSex_     As Boolean

'// Ghi thuộc tính Tên (từ giá trị bên ngoài lớp truyền vào)
Public Property Let strName(ByRef strValue As String)
    strName_ = strValue
End Property

'// Đọc thuộc tính Tên (từ lớp) cho môi trường bên ngoài lớp
Public Property Get strName() As String
    strName = strName_
End Property

'// Ghi thuộc tính Màu lông (từ giá trị bên ngoài lớp truyền vào)
Public Property Let strColor(ByRef strValue As String)
    strColor_ = strValue
End Property

'// Đọc thuộc tính Màu lông (từ lớp) cho môi trường bên ngoài lớp
Public Property Get strColor() As String
    strColor = strColor_
End Property

'// Ghi thuộc tính Giới tính (từ giá trị bên ngoài lớp truyền vào)
Public Property Let blnSex(ByRef blnValue As Boolean)
    blnSex_ = blnValue
End Property

'// Đọc thuộc tính Giới tính (từ lớp) cho môi trường bên ngoài lớp
Public Property Get blnSex() As Boolean
    blnSex = blnSex_
End Property
Như vậy, cách tổng quát để tạo 1 thuộc tính cho lớp là :
+ Tạo các biến trung gian với tầm vực Riêng tư (Private) (không bắt buộc, tùy thuộc vào cách viết code của mỗi người)
+ Tạo thuộc tính đọc :
PHP:
Public | Friend Property Get <Tên thuộc tính>() As Kiểu dữ liệu
    <Tên thuộc tính> = <Biến trung gian tương ứng>
End Property
+ Tạo thuộc tính ghi :
- Đối với thuộc tính không phải là đối tượng trong lớp :
PHP:
Public | Friend Property Let <Tên thuộc tính>(ByVal | ByRef <Giá trị truyền vào> As Kiểu dữ liệu)
    <Biến trung gian tương ứng> = <Giá trị truyền vào> 'Ghi đầy đủ phải là : Let <Biến trung gian tương ứng> = <Giá trị truyền vào>
End Property
- Đối với thuộc tính là đối tượng trong lớp (kiểu dữ liệu của thuộc tính là đối tượng) :
PHP:
Public | Friend Property Set <Tên thuộc tính>(ByVal | ByRef <Giá trị truyền vào> As Kiểu dữ liệu)
    Set <Biến trung gian tương ứng> = <Giá trị truyền vào>
End Property
*Từ Let có nghĩa là gán giá trị cho biến, thuộc tính... (chúng ta có thể bỏ qua từ khóa này khi gán giá trị cho biến, thuộc tính không phải là đối tượng)

*Lưu ý về tầm vực của các thành phần trong lớp :
+ Tầm vực Public : Cho phép môi trường bên ngoài có thể truy cập được thành phần này của lớp (thuộc tính, phương thức, sự kiện), kể cả môi trường bên ngoài dự án này (Project khác).​
+Tầm vực Friend : Cho phép môi trường bên ngoài lớp trong cùng dự án (Project) có thể truy cập được thành phần này của lớp (thuộc tính, phương thức), môi trường bên ngoài dự án này (Project khác) không thể truy cập thành phần này của lớp.​
+ Tầm vực Private : Riêng tư trong lớp (áp dụng với phương thức. Chúng ta vẫn có thể tạo thuộc tính có tầm vực Riêng tư nhưng nó không có ý nghĩa, bản thân lớp cũng không thể truy cập thuộc tính có tầm vực Riêng tư của chính nó).​
+ Tầm vực của các Sự kiện trong lớp luôn là Public (dù có khai báo là Public hay không. Chúng ta không thể khai báo tầm vực thấp hơn cho Sự kiện của lớp).​
(Hết bài 4)

Phụ lục 1 : Thuộc tính bổ sung
Phần trên, chúng ta đã tìm hiểu cách xây dựng thuộc tính mô tả những đặc điểm của lớp. Đây là phần mở rộng dành cho những bạn nào quan tâm đến những thuộc tính bổ sung cho lớp.

- Thuộc tính mặc định : Khi bạn lập trình với 1 số đối tượng điều khiển (control) trong UserForm, như Label, TextBox... khi muốn ghi chữ cho chúng, ta viết code :
PHP:
<Tên Label>.Caption = "Tên của Label"
<Tên TextBox>.Text = "Giá trị của Textbox"
Vì đây là những thuộc tính mặc định của những điều khiển trên, chúng ta cũng có thể viết tắt như sau :
PHP:
<Tên Label> = "Tên của Label"
<Tên TextBox> = "Giá trị của Textbox"
Khi xây dựng lớp, chúng ta cũng có thể tạo ra những lớp có những thuộc tính mặc định như vậy. Ở ví dụ này tôi sẽ hướng dẫn các bạn các bước để tạo thuộc tính mặc định là Tên cho lớp Mèo :
Bước 1 : Thêm mới lớp Mèo, gỡ ra khỏi dự án, có lưu tệp kết xuất.
Bạn cần đăng nhập để thấy đính kèm

Bước 2 : Mở tệp vừa kết xuất bắng 1 trình soạn thảo văn bản (ở đây tôi dùng Notepad++, các bạn cũng có thể dùng những trình soạn thảo khác như Notepad, Word, ...)
Bước 3 : Thêm dòng code này :
PHP:
Attribute strName.VB_UserMemId = 0
Bên dưới dòng :
PHP:
Public Property Get strName() As String
Sau đó lưu và đóng tệp này lại.
Bước 4 : Import lại tệp vừa sửa vào dự án của bạn (import lại class cCat.cls). Xong

Các bạn kiểm tra xem thuộc tính mặc định của lớp Mèo bằng thủ tục nhỏ sau nhé :
PHP:
Public Sub TestDefaultProperty()
    Dim clsCat As cCat

    Set clsCat = New cCat
        clsCat = "Tom"        'Ghi đầy đủ phải là : Let clsCat.strName = "Tom"

    MsgBox "Name of clsCat is " & clsCat

    Set clsCat = Nothing
End Sub
Tham khảo :
Thảo luận về sự giống và khác nhau giữa Attribute và Property :
Xây dựng thuộc tính mặc định cho lớp :

- Một số gợi ý liên quan các bạn tự nghiên cứu nhé :
+ Tạo diễn giải (tooltip) trong cửa sổ cho lớp / đối tượng như hình dưới :
Bạn cần đăng nhập để thấy đính kèm

+Tạo lớp / đối tượng dựng sẵn (tôi sẽ giải đáp trong 1 chủ đề khác)

Phụ lục 2 : Lỗi cú pháp Get Property trong trình biên dịch VBE.
Phụ lục này cũng là 1 phần mở rộng dành cho những bạn nào muốn nghiên cứu thêm. Đây là 1 chủ đề khá hay trên Twitter (không có link gốc) được dẫn lại tại trang này . Tôi lược dịch lại để các bạn tiện theo dõi.

Tôi (tác giả bài viết này, không phải @thaipv) đã thấy chủ đề này được đăng bởi @RubberduckVBA trên Twitter và không thể cưỡng lại việc chia sẻ nó ở đây. Bạn có biết rằng mã VBA này sẽ biên dịch và chạy chính xác những dòng code này không ?
PHP:
Public Property Get Foo() As String
    Foo = "WTF!"
End Function
(Bên trên ghi Property, bên dưới kết thúc là Function)

Và, có lẽ còn đáng sợ hơn, những điều này (và các biến thể tương tự) cũng chạy được luôn !
PHP:
Public Property Get Foo2() As String
    Foo2 = "WTF!"
End Sub
PHP:
Public Property Get Foo3() As String
    Foo3 = "WTF!"
    Exit Function
    Exit Sub
End Property
-> Ý nghĩa của hàm / thủ tục / phương thức / thuộc tính trong VBA chỉ có ý nghĩa tương đối. Chúng đều là những chương tình con. Thuộc tính là (2) hàm rất đặc biệt, (chúng) cho phép ta ghi (write) hay đọc (read) giá trị bên trong lớp.
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Bài 5 : Phương thức

Chào các bạn. Tiếp tục bài học về Lập trình hướng đối tượng trong VBA, hôm nay tôi giới thiệu với các bạn về Phương thức trong lớp.

Theo phương pháp lập trình hướng thủ tục, chúng ta tách chương trình thành những chương trình con (hàm, thủ tục) được thực hiện theo các tình huống và lặp lại nhiều lần.

Theo phương pháp pháp lập trình hướng đối tượng, mục tiêu của chương trình là xử lý dữ liệu (những hành vi của đối tượng hay chính là phương thức của lớp). Sự thay đổi căn bản ở chỗ, một chương trình hướng đối tượng được thiết kế xoay quanh dữ liệu mà chúng ta có thể làm việc trên đó, hơn là theo bản thân chức năng của chương trình (Thực chất đây không phải là một phương pháp mới mà là một cách nhìn mới trong việc lập trình (beta.wikiversity.org/wiki))

Thảo luận sự giống và khác nhau giữa POP (lập trình hướng thủ tục) và OOP (lập trình hướng đối tượng) các bạn tham khảo tại địa chỉ sau :

Tình huống : Tính lương cơ bản cho nhân viên làm việc trong tháng. (Lương cơ bản = Hệ số x Số ngày làm việc thực tế / Số ngày làm việc tiêu chuẩn x Lương cơ bản)
STTIDHọ và tênHệ sốNgày làm việc
tiêu chuẩn
Ngày làm việc
thực tế
Luơng
cơ bản
Ghi chú
1annvNguyễn Văn An1.2526248.000.000
2binhptPhạm Thị Bình1.2526268.000.000
3cucltLê Thị Cúc0.80262610.000.000Thử việc
4hungtvTrần Văn Hùng1.502624.56.000.000

Bài giải 1 : Lập trình hướng thủ tục : Viết 1 hàm tính lương tháng.
PHP:
Public Function MonthlySalary(ByRef sngRatio As Single, _
                              ByRef sngStandardDay As Single, _
                              ByRef sngActualDay As Single, _
                              ByRef sngContractSalary As Single) As Single
    MonthlySalary = sngRatio * (sngActualDay / sngStandardDay) * sngContractSalary
End Function
Như vậy, lương tháng của từng nhân viên trong công ty tháng này là :
+ Lương(annv) = MonthlySalary(1.25, 26, 24, 8000000)
+ Lương(binhpt) = MonthlySalary(1.25, 26, 26, 8000000)
+ Lương(cuclt) = MonthlySalary(0.80, 26, 26, 10000000)
+ Lương(hungtv) = MonthlySalary(1.50, 24.5, 6000000)

Bài giải 2 : Lập trình hướng đối tượng. Chúng ta xây dựng lớp cStaff bao gồm 4 thuộc tính (chỉ ghi)(hệ số, ngày làm việc tiêu chuẩn, ngày làm việc thực tế, lương cơ bản. Thuộc tính ID, họ tên và ghi chú chúng ta chưa quan tâm ở tình huống này, nhưng chúng ta cũng hoàn toàn có thế mở rộng thêm khi cần) và 1 phương thức (tính lương - GetMonthlySalary) như sau :

PHP:
Option Explicit

'// Khai báo các biến tạm
Private sngRatio_           As Single
Private sngStandardDay_     As Single
Private sngActualDay_       As Single
Private sngContractSalary_  As Single

'// Các thuộc tính của Nhân viên (thuộc tính chỉ ghi)
Friend Property Let sngRatio(ByRef sngValue As Single)
    sngRatio_ = sngValue
End Property
Friend Property Let sngStandardDay(ByRef sngValue As Single)
    sngStandardDay_ = sngValue
End Property
Friend Property Let sngActualDay(ByRef sngValue As Single)
    sngActualDay_ = sngValue
End Property
Friend Property Let sngContractSalary(ByRef sngValue As Single)
    sngContractSalary_ = sngValue
End Property

'// Phương thức tính lương cơ bản trong tháng
Friend Function GetMonthlySalary() As Single
    GetMonthlySalary = sngRatio_ * (sngActualDay_ / sngStandardDay_) * sngContractSalary_
End Function
Cách sử dụng : Trên Module bạn chép đoạn thủ tục sau :
PHP:
Option Explicit

Public Sub CalMonthlySalary()
    Dim clsStaff1 As cStaff
    Dim clsStaff2 As cStaff
    Dim clsStaff3 As cStaff
    Dim clsStaff4 As cStaff

    '// Gán thuộc tính cho clsStaff1 và ghi kết quả tính lương ra Immediate
    Set clsStaff1 = New cStaff
        clsStaff1.sngRatio = 1.25
        clsStaff1.sngStandardDay = 26
        clsStaff1.sngActualDay = 24
        clsStaff1.sngContractSalary = 8000000
    Debug.Print clsStaff1.GetMonthlySalary

    '// Gán thuộc tính cho clsStaff2 và ghi kết quả tính lương ra Immediate
    Set clsStaff2 = New cStaff
        clsStaff2.sngRatio = 1.25
        clsStaff2.sngStandardDay = 26
        clsStaff2.sngActualDay = 26
        clsStaff2.sngContractSalary = 8000000
    Debug.Print clsStaff2.GetMonthlySalary

    '// Gán thuộc tính cho clsStaff3 và ghi kết quả tính lương ra Immediate
    Set clsStaff3 = New cStaff
        clsStaff3.sngRatio = 0.8
        clsStaff3.sngStandardDay = 26
        clsStaff3.sngActualDay = 26
        clsStaff3.sngContractSalary = 10000000
    Debug.Print clsStaff3.GetMonthlySalary

    '// Gán thuộc tính cho clsStaff4 và ghi kết quả tính lương ra Immediate
    Set clsStaff4 = New cStaff
        clsStaff4.sngRatio = 1.5
        clsStaff4.sngStandardDay = 26
        clsStaff4.sngActualDay = 24.5
        clsStaff4.sngContractSalary = 6000000
    Debug.Print clsStaff4.GetMonthlySalary
   
    '// Giải phóng bộ nhớ và thoát khỏi chương trình
    Set clsStaff1 = Nothing
    Set clsStaff2 = Nothing
    Set clsStaff3 = Nothing
    Set clsStaff4 = Nothing
End Sub
Lưu ý quan trọng : Khi lập trình, để giảm thiểu tối đa những tác động không mong muốn, chúng ta nên thiết lập những tầm vực thấp nhất có thể cho mỗi thành phần trong chương trình. (Như vậy, thứ tự tầm vực ưu tiên khi lập trình là Dim | Static | Private | Friend | Public (Global) | Ghi dữ liệu ra bên ngoài chương trình...).

Trong bài giải 2, tôi thiết lập tầm vực cho thuộc tính và phương thức của lớp cStaff đều là Friend vì chúng đều cần những tác động từ bên ngoài lớp gồm các thành phần trong cùng dự án (Project) nhưng không cần thiết cho các dự án bên ngoài (Project khác).

Lưu ý khác : Kể từ bài Thuộc tính và bài này (Phương thức), các bạn mới tìm hiểu về phương pháp lập trình hướng đối tượng sẽ thấy cách viết code khá rườm rà so với phương pháp cũ (lập trình hướng thủ tục). Thực tế, đây đều là những kiến thức cũ (hằng, biến, chương trình con) nhưng được viết lại bằng phương pháp mới. Bởi vì chúng (hằng, biến, chương trình con) chưa có sự tác động qua lại lẫn nhau, nên chúng ta chưa thấy được sự ưu việt của lớp / đối tượng trong VBA.

Tôi kết thúc bài này tại đây. Chào các bạn và hẹn sớm gặp lại.
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Bài 6 : Bắt sự kiện (1/2)

Chào các bạn, tiếp tục chủ đề lập trình hướng đối tượng trong VBA, hôm nay tôi giới thiệu với các bạn về chủ đề Bắt sự kiện của đối tượng.

Trước khi bắt đầu bài học, chúng ta cùng thư giãn trong không khí bóng đá với trận cầu Việt Nam - Malaysia đã diễn ra vào ngày 10/10/2019 trong khuôn khổ vòng loại World Cup 2022 nhé !
Bạn cần đăng nhập để thấy đa phương tiện

Một số điểm nhấn trong trận đấu :
+ Phút 28 : Từ một pha phối hợp ở biên trái rất đẹp mắt, đối tượng Hùng Dũng chuyền bóng (phương thức và tạo sự kiện) để đối tượng Quang Hải (bắt sự kiện chuyền bóng) sút nối chân trái làm tung lưới khung thành Malaysia nhưng rất tiếc, đối tượng trọng tài biên đã căng cờ việt vị (bắt sự kiện Quang Hải chạm bóng và phương thức căng cờ). (Những) đối tượng người hâm mộ đội tuyển VN có một phen mừng hụt (bắt sự kiện rung lưới và phương thức ăn mừng).

+ Phút 40 : Đối tượng Quế Ngọc Hải chuyền bóng (phương thức và tạo sự kiện) rất chuẩn xác. Đối tượng Quang Hải thoát xuống hợp lệ (bắt sự kiện chuyền bóng), vặn lườn (phương thức) đối tượng hậu vệ đối phương rồi ngả người vô lê (phương thức) cực dẻo. Mặc dù đối tượng thủ môn Malaysia đã khép góc (bắt sai sự kiện, phương thức khép góc) nhưng lưới của đội bạn đã rung lên. Anh (đối tượng thủ môn đội bạn) rất đẹp trai (thuộc tính) nhưng chúng tôi rất tiếc. Bàn thắng được công nhận, đội tuyển VN vươn lên dẫn trước. Sân vận động Mỹ Đình nổ tung, đối tượng người hâm mộ đội tuyển VN vỡ òa (bắt sự kiện Vào, phương thức ăn mừng) trong vui sướng !

Như vậy, thông qua bình luận trên, chúng ta thấy rằng Lập trình hướng đối tượng cũng rất gần gũi với môi trường thực tế :

+ Đối tượng có thể là bất cứ thứ gì có thể tác động đến môi trường đang xem xét (trận đấu bóng đá) (trong ví dụ trên thì con người bao gồm các cầu thủ thi đấu trên sân, trọng tài biên và người hâm mộ là những đối tượng được quan tâm. Thực ra còn rất nhiều đối tượng khác nữa như quả bóng, khung thành, cờ, sân vận động...).

+ Các đối tượng có những tính chất, đặc điểm (cao, gầy, đen, hôi, đẹp trai ....) và những phương thức (chuyền bóng, sút nối, căng cờ, ăn mừng, ....) (phần này chúng ta đã học ở những bài trước nên tôi lướt sơ qua)

+ Quan trọng nhất, các đối tượng có thể gửi những thông điệp qua lại cho nhau (gửi thông điệp = tạo sự kiện, nhận thông điệp = bắt sự kiện). Trong ví dụ trên, Quế Ngọc Hải chuyền bóng (phương thức chuyền bóng và đồng thời gửi thông điệp là đã chuyền bóng lên phía trên = tạo sự kiện). Các đối tượng khác (trong đó có Quang Hải) nhận thông điệp này (bắt sự kiện) và có những phản ứng phù hợp với thông điệp nhận được. Khi Quang Hải nhận được bóng, anh cũng gửi thông điệp đi (tạo sự kiện). Những đối tượng nhận thông điệp này lại có những phản ứng khác nhau có thể là :
- Các cầu thủ VN trên sân : Tiến lên anh em, ...​
- Người hâm mộ đội tuyển VN : Sút đi Hải ơi, reo hò, ...​
- Các cầu thủ đội bạn : Nguy rồi, rút về ngay, ...​
- Thủ môn đội bạn : Suy đoán Quang Hải sút góc nào và khóa chặt góc đó hoặc dâng lên ôm bóng, .... (Trong trường hợp này, như các bạn đã xem thủ môn đội bạn đã khép góc nhưng chậm hơn Quang Hải - anh đã bắt sai sự kiện. Anh phải bắt sự kiện lúc Quế Ngọc Hải ra chân chuyền bóng mới đúng, bắt sự kiện Quang Hải nhận được bóng thì trễ rồi !)​
- ...​
Trong lập trình cũng vậy, điều quan trọng nhất trong việc bắt sự kiện là đặt code vào đúng chỗ. (Yêu đúng người thôi chưa đủ, phải đúng thời điểm nữa nhé các bạn !)

Tôi kết thúc bài giới thiệu về Bắt sự kiện tại đây. Bài tiếp theo, chúng ta sẽ thực hành 1 số code về chủ đề này nhé.
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Bài 6 : Bắt sự kiện (2/2)

Chào các bạn, hôm nay tôi tiếp tục phần 2 bài học về Bắt sự kiện của đối tượng trong VBA.

Trước khi bắt đầu bài học, chúng ta cùng điểm qua một số phong cách viết hàm (gọi chung cho các chương trình con) trong VBA (tôi đã viết sơ qua trong Ghi chú 1 của Bài học số 1) :
+ Phong cách viết hàm Xuôi : truyền giá trị bên ngoài vào hàm thông qua biến.
+ Phong cách viết hàm Ngược : truyền giá trị bên trong hàm ra bên ngoài thông qua biến.
+ Phong cách viết hàm Hỗn hợp : truyền giá trị vào hàm và từ hàm truyền giá trị ra bên ngoài thông qua biến.

Khi bắt sự kiện của đối tượng tức là chúng ta (đang ở thế bị động) muốn nhận thông điệp từ đối tượng gửi đi. Và do đó, phong cách viết hàm trong trường hợp này là phong cách Ngược, tức là chúng ta nhận những thông điệp của đối tượng thông qua biến của hàm (chính xác thì đây là phong cách viết hàm Hỗn hợp vì chúng ta cũng có thể gửi lại thông điệp cho đối tượng thông qua biến).

Trở lại với môi trường lập trình trong VBA, mỗi đối tượng (application, workbook, worksheet, userform, control, ...) có rất nhiều thông điệp gửi đi (có bao nhiêu thì tùy thuộc vào người lập trình, ở đây là Microsoft, bên thứ 3 và chính chúng ta, ...). Có 2 cách để bắt sự kiện của 1 đối tượng trong VBA :

+ Cách 1 : Bắt sự kiện bên trong chính đối tượng đang xem xét (viết chương trình cho những kịch bản dựng sẵn). Chẳng hạn, để bắt sự kiện khi người dùng thay đổi ô được chọn trên sheet nào đó, chúng ta làm như sau :

Bước 1 : Vào cửa sổ VBE, click đúp vào đối tượng cần bắt sự kiện (trong hình là Sheet1).
Bạn cần đăng nhập để thấy đính kèm


Bước 2 : Trong cửa sổ soạn code, click vào nút thả xuống (Drop Down) bên trái \ Chọn đối tượng cần bắt sự kiện.
Bạn cần đăng nhập để thấy đính kèm

Bước 3 : Cũng trong cửa sổ này, click vào nút thả xuống (Drop Down) bên phải \ Chọn sự kiện cần bắt.
Bạn cần đăng nhập để thấy đính kèm

Khi chọn đối tượng ở bước 2 thì sẽ có 1 sự kiện mặc định được chọn. Với đối tượng worksheet thì sự kiện mặc định được chọn là SelectionChange. Nếu sự kiện mặc định này bạn không quan tâm thì bạn có thể xóa đi và chọn lại ở bước 3. Nếu sự kiện bạn đang cần bắt đã tự động được chon ở bước 2 thì bạn cũng không cần làm bước 3 nữa.

Một sự kiện của 1 đối tượng được viết theo phong cách viết hàm ngược (như đã trình bày ở trên) thường được cấu trúc như sau :
PHP:
Private Sub <Tên đối tượng>_<Tên sự kiện>(Byval | Byref <Ds các biến> As <Kiểu dữ liệu>)
(Các biến ở đây chính là những thông điệp của đối tượng gửi ra bên ngoài khi xảy ra sự kiện này)

Bước 4 : Chọn lại bước 3 với những sự kiện khác mà bạn quan tâm.

Lưu ý : Cách này chỉ áp dụng được với những đối tượng tạo ra được trong thời gian Design Time như WorkBook, WorkSheet, UserForm, Control,... ; KHÔNG ÁP DỤNG được với những đối tượng tự tạo (được dựng từ class, ...).

+ Cách 2 : Bắt sự kiện của đối tượng khác trong đối tượng đang xem xét (viết những phản ứng của đối tượng đang xem xét với những sự kiện xảy ra của đối tượng khác). Chẳng hạn, khi bạn đang làm việc với UserForm (đối tượng đang xem xét), và đồng thời bạn muốn bắt sự kiện bắt sự kiện người dùng thay đổi ô được chọn trên Sheet1 (như ở cách 1), thì bạn làm như sau :

Bước 1 : Trên UserForm, bạn khai báo 1 biến đối tượng cùng với sự kiện của nó (bài số 2) có tầm vực module.
PHP:
Private WithEvents wshSheet1 As Worksheet
Bước 2 : Trỏ đến địa chỉ trên vùng nhớ của biến đối tượng này. Thông thường, chúng ta sẽ làm bước này ở thủ tục khởi tạo UserForm :
PHP:
Private Sub UserForm_Initialize()
    ...

    '// Trỏ biến wshSheet1 đến vùng nhớ của đối tượng Sheet1
    Set wshSheet1 = ThisWorkbook.Worksheets("Sheet1")

    ...
End Sub
Bước n : Các bước tiếp theo chúng ta làm tương tự cách 1.

Ví dụ 1 : Tô sáng dòng và cột của ô đang hoạt động (ô được chọn) trong Excel (Tạo Highlight ô thay đổi trên WorkSheet). Sau đây tôi sẽ gợi ý các bạn 1 cách viết code đơn giản (đây chỉ là 1 ví dụ để chúng ta làm quen với bắt sự kiện).

Bước 1 : Xác định đối tượng. Nếu viết code cho tệp Excel riêng, nhân vật chính của chúng là ThisWorkbook (có thể bắt sự kiện theo cả 2 cách như tôi đã trình bày ở trên). Ngược lại, nếu code này áp dụng cho tất cả các tệp Excel đang mở (tạo Addin), nhân vật chính của chúng ta là ActiveWorkbook (chỉ có thể bắt sự kiện theo cách 2). Trong ví dụ này, tôi chọn ThisWorkbook (trường hợp viết Addin, các bạn làm tương tự).

Bước 2 : Xác đinh sự kiện của đối tượng. Trong bài này, chúng ta cần tô sáng vùng được chọn, vì vậy sự kiện cần bắt là SheetSelectionChange. (Đối tượng ThisWorkbook có rất nhiều sự kiện. Nếu chúng ta bắt sự kiện Open / Activate / SheetActivate, tức là trước sự kiện SheetSelectionChange thì chúng ta rơi vào thế 'việt vị' rồi nhé. Ngược lại, nếu chúng ta bắt sự kiện SheetDeactivate, tức là sau sự kiện SheetSelectionChange thì đã là quá trễ, code mất tác dụng).

Bước 3 : Bắt đầu viết code
PHP:
Option Explicit

Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
    Static rngLastArea As Range 'Biến này dùng để trả lại màu cho hàng và cột trước đó

    If rngLastArea Is Nothing Then
        Set rngLastArea = Target
    ElseIf Not rngLastArea Is Target Then
        '// Trả lại không màu cho hàng và cột cũ
        Columns(rngLastArea.Column).Interior.ColorIndex = xlNone
        Rows(rngLastArea.Row).Interior.ColorIndex = xlNone

        '// Tô màu cho vùng mới
        Columns(Target.Column).Interior.Color = vbYellow
        Rows(Target.Row).Interior.Color = vbYellow

        '// Không tô màu ô đang hoạt động
        ActiveCell.Interior.ColorIndex = xlNone

        '// Đặt lại biến nhớ rngLastArea
        Set rngLastArea = Target
    End If
End Sub
Đây là ví dụ khá điển hình về bắt sự kiện. Nhược điểm của cách này là sẽ làm mất định dạng của vùng được chọn. Và thông qua ví dụ này, tôi đã giới thiệu với các bạn về :
+ Sự kiện SheetSelectionChange của đối tượng ThisWorkBook (Tương tự, các bạn nghiên cứu sự kiện SelectionChange của đối tương WorkSheet).​
+ Một ví dụ về cách sử dụng tầm vực Static của biến trong VBA.​
Tải file mẫu :

Ví dụ 2 : Tô màu nút bấm khi chuột rê qua và trả lại màu cho nút bấm khi chuột rời khỏi nút bấm (Command trong UserForm)
Bạn cần đăng nhập để thấy đính kèm

Bước 1 : Xác định đối tượng. Trong bài này tôi giả sử UserForm của chúng ta có 2 nút bấm là cmdLButton và cmdRButton. Đối tượng chúng ta cần quan tâm là cmdLButton, cmdRButton và UserForm (tại sao chúng ta lại cần quan tâm đối tượng này tôi sẽ giải thích ở bước 2)

Bước 2 : Xác định sự kiện của đối tượng. Khi người dùng rê chuột vào nút cmdLButton thì nút này được tô màu, và đồng thời trả lại màu bình thường cho nút cmdRButton; và ngược lại. Trong trường hợp người dùng rê chuột vào UserForm thì chúng ta phải trả lại màu bình thường cho cả 2 nút bấm trên (đây là lý do chúng ta cần quan tâm đến đối tượng UserForm - đối tượng giữ vai trò làm nền trung tâm. Trong trường hợp các bạn thiết kế nút cmdLButton và cmdRButton trên nền 1 đối tượng nền khác như Frame, TabStrip hay MultiPage thì đối tượng nền trung tâm là Frame, TabStrip hay MultiPage tương ứng).

Bước 3 : Viết code
PHP:
Option Explicit

'// Trung tâm tô màu (hàm chính)
Private Sub GetColor(ByRef strGroupHover As String)
    Static strAccentGroup As String 'Biến nhớ đối tượng đang tô sáng hiện thời

    If strGroupHover <> strAccentGroup Then
        Dim lngBackClr As Long
 
        '// Tô màu cho nút #cmdLButton
        lngBackClr = IIf("cmdLButton" = strGroupHover, vbYellow, vbWhite)
        If lngBackClr <> cmdLButton.BackColor Then cmdLButton.BackColor = lngBackClr
 
        '// Tô màu cho nút #cmdRbutton
        lngBackClr = IIf("cmdRButton" = strGroupHover, vbYellow, vbWhite)
        If lngBackClr <> cmdRButton.BackColor Then cmdRButton.BackColor = lngBackClr
 
        '// Gán lại biến nhớ
        strAccentGroup = strGroupHover
    End If
End Sub
'// Bắt sự kiện rê chuột trên các đối tượng cmdLButton, cmdRButton và đối tượng nền trung tâm là UserForm
Private Sub cmdLButton_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single): Call GetColor("cmdLButton"): End Sub
Private Sub cmdRButton_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single): Call GetColor("cmdRButton"): End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single):   Call GetColor(vbNullString): End Sub
Tải file mẫu :

Một số chủ đề gợi ý chúng ta cần nghiên cứu :
1. Bắt sự kiện của đối tượng đặc biệt Application : Auo_ Open, Auto_Close, OnTime, OnKey (?...)
2. Thứ tự thực hiện các sự kiện : Auto_ Open, Workbook_Open, OnLoad (trong tùy biến Ribbon).
Tương tự, thứ tự thực hiện các sự kiện : Auto_Close, Workbook_Close​
3. Sự kiện khởi tạo và phá hủy lớp / đối tượng.
4. Thuộc tính Application.EnableEvents tác động đến những đối tượng nào ?
5. Bắt và thay đổi tầm vực của sự kiện ( -> Sự kiện và thủ tục là chương trình con, chúng chỉ có ý nghĩa tương đối)
6. Bắt sự kiện để chặn sự kiện đó của chính đối tượng đang xem xét !
7. Kỹ thuật subclassing / hook để bắt những sự kiện của một số đối tượng.
8. Kỹ thuật multicasting để cải tổ đối tượng (tôi sẽ trở lại chủ đề này).
9. Nhiều đối tượng cùng bắt 1 sự kiện của 1 đối tượng để chạy đa luồng (multi thead) trong VBA ? (Tôi sẽ trở lại chủ đề này. Tuy nhiên, rất đáng tiếc là không thể chạy đa luồng trong VBA theo cách này được !)

Tôi kết thúc bài học về Bắt sự kiện ở đây. Bài sau chúng ta sẽ học về Tạo sự kiện. Xin chào và hẹn sớm gặp lại !
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Bài 7 : Tạo sự kiện (1/2)

Chào các bạn, tiếp tục chủ đề lập trình hướng đối tượng trong VBA, hôm nay tôi giới thiệu với các bạn về chủ đề Tạo sự kiện cho lớp / đối tượng. (Nếu đúng theo trình tự, bài này phải được viết trước bài số 6 - bắt sự kiện. Tuy nhiên, vì đây là 1 bài khó, và chúng ta cần có những ý niệm ban đầu về sự kiện của đối tượng trước nên tôi đặt bài này phía sau bài bắt sự kiện).

Nhắc lại, các đối tượng có thể gửi những thông điệp qua lại cho nhau (gửi thông điệp = tạo sự kiện, nhận thông điệp = bắt sự kiện). Trở lại trận bóng giữa Việt Nam và Malaysia, có rất nhiều thông điệp được gửi đi giữa những cầu thủ (nhiều sự kiện được tạo). Chẳng hạn, tình huống ở phút 40, khi Quế Ngọc Hải chuyền bóng, anh đồng thời gửi thông điệp (tạo sự kiện) đến các đồng đội của anh (trong đó có Quang Hải). Khi Quang Hải thoát xuống, lúc di chuyển hay lúc nhận được bóng, anh đều gửi những thông điệp (tạo những sự kiện) cho mọi người. Thế giới thực luôn có những thông điệp được gửi đi (có rất nhiều sự kiện). Theo một cách nào đó, mỗi người lại có những cách tiếp nhận thông tin (bắt những thông điệp - sự kiện) khác nhau - đó là cách não bộ con người xử lý.

Chương trình Excel cũng có rất nhiều sự kiện (chẳng hạn thay đổi giá trị của biến, thay đổi vùng chọn, kích hoạt vùng nào đó, thay đổi công thức trên ô, chuyển qua lại giữa các sheet...). Tuy nhiên, máy tính không thông minh như con người, nó không tự nhận biết được đây là những sự kiện mà đơn giản chỉ làm theo những gì đã được viết sẵn bởi lập trình viên. Vì vậy, công việc của chúng ta là phải định nghĩa cho máy tính biết đâu là sự kiện (nội dung bài này) và làm gì với những tình huống cho trước (nội dung bài bắt sự kiện). Hay nói cách khác, việc tạo sự kiện cho đối tượng là viết tên cho sự kiện, kèm theo đó gửi đúng lúc những giá trị của những biến liên quan ra môi trường bên ngoài.

Ví dụ 1 : Thiết kế giao diện chương trình theo bộ màu sắc (có 1 màu nhấn), người dùng có thể chọn tone màu theo danh mục có sẵn. Giả sử trong chương trình Excel của chúng ta có nhiều UserForm. Mỗi UserForm lại có 1 số điều khiển (Control). Yêu cầu đề ra là :
+ Tô màu cho Control khi chuột rê qua và trả lại màu cho Control này khi chuột rời khỏi nó (Ví dụ 2 bài số 6 phần 2/2).​
+ Tô màu lại cho các Control ngay lập tức khi người dùng thay đổi tone màu.​
Các bạn xem hình sau để hiểu thêm yêu cầu nhé :
Bạn cần đăng nhập để thấy đính kèm

Ghi chú : Bài này không chỉ áp dụng cho đối tượng màu sắc, chúng ta còn có thể áp dụng những lý thuyết này để xây dựng những đối tượng trung tâm khác như Ngôn ngữ, Các tham số hệ thống, Tracker (trung tâm theo dõi)...

Sau đây là 2 cách giải, các bạn tham khảo nhé :
Bài giải 1 : Lập trình hướng thủ tục
Code trong module như sau :
Mã:
Option Explicit

Private MainColor As Byte
Private arrColors As Variant

'// Hàm này để lấy màu nhấn
Public Function GetAccentClr() As Long
    GetAccentClr = GetColor(1)
End Function

'// Hàm này để lấy màu thường
Public Function GetNormalClr() As Long
    GetNormalClr = GetColor(0)
End Function

'// Thủ tục này để thay đổi màu nhấn
Public Sub ChangeTone(ByRef bytTheme As Byte)
    If MainColor <> bytTheme Then
        MainColor = bytTheme: Call GetColors

        '// Tô lại màu của tất cả các đối tượng
        frmExample.RefillClr
        frmOptions.RefillClr
    End If
End Sub

'// 2 hàm này để lấy màu, hỗ trợ cho 2 hàm lấy màu phía trên
Private Function GetColor(ByRef bytIndex As Byte) As Long
    If vbEmpty = VarType(arrColors) Then arrColors = Array(vbWhite, 10158219) 'Tím
    GetColor = arrColors(bytIndex)
End Function
Private Sub GetColors()
    Select Case MainColor        'Normal   Accent
        Case 0: arrColors = Array(vbWhite, 10158219)    'Tím
        Case 1: arrColors = Array(vbWhite, 5287936)     'Xanh lá
        Case 2: arrColors = Array(vbWhite, 9471)        'Do tuoi
        Case 3: arrColors = Array(vbWhite, 49151)       'Hô phách
    End Select
End Sub

'// 2 thủ tục này để hiện từng form
Public Sub ShowExampleForm()
    frmExample.Show
End Sub
Public Sub ChangeToneColor()
    frmOptions.Show
End Sub
Các bạn lưu ý dòng 22 và 23 : Nếu trong Project của chúng ta có bao nhiêu đối tượng cần thay đổi màu thì chúng ta phải viết số dòng tương ứng. Khi có sự thay đổi tone màu chính, tất cả các đối tượng có sử dụng màu cũng cần phải làm mới (thủ tục .RefillClr). Nếu đối tượng đang mở thì không có vấn đề gì. Trong trường hợp đối tượng chưa được dựng thì chương trình sẽ tự mở ngầm và tô lại màu => Tốn tài nguyên hệ thống.

Tải file mẫu :

Bài giải 2 : Lập trình hướng sự kiện
Chúng ta xây dựng 1 đối tượng màu sắc trung tâm (đối tượng này được dựng sẵn, không cần phải dựng bằng từ khóa Set ... = New ... nữa). Đối tượng này có :
+ 2 thuộc tính là AccentClr và NormalClr (để người dùng lấy màu),
+ 1 phương thức tầm vực Friend là ChangeTone (để người dùng thay tone màu) và,
+ 1 sự kiện cần xây dựng là ChangeColor. Khi người dùng thay đổi tone màu, chúng ta cần gửi màu đã thay đổi ra môi trường ngoài nên sự kiện ChangeColor này có 2 biến là clrAccent và clrNormal (tương ứng với 2 thuộc tính của đối tượng này. Chúng ta cũng có thể không cần gửi 2 biến này ra bên ngoài nhưng khi đó người dùng sẽ phải thêm 1 bước nữa là lấy giá trị từ thuộc tính tương ứng)

Trong ví dụ này, chúng ta khai báo sự kiện ChangeColor như sau :
PHP:
Public Event ChangeColor(ByRef clrAccent As Long, ByRef clrNormal As Long)
Khi người dùng thay đổi tone màu (xem lại cách giải 1) thì là lúc chúng ta thông báo sự kiện ChangeColor và gửi 2 biến ra bên ngoài (màu nhấn và màu thường). Chúng ta thay dòng 22, 23, ... (những dòng thay đổi màu của tất cả các đối tượng liên quan) thành 1 dòng sau :
PHP:
RaiseEvent ChangeColor(CLng(arrColors(1)), CLng(arrColors(0)))
Như vậy, toàn bộ code của đối tượng oColor như sau :
PHP:
Option Explicit
Option Base 0

Public Event ChangeColor(ByRef clrAccent As Long, ByRef clrNormal As Long)

Private MainColor As Byte
Private arrColors As Variant

Private Sub Class_Initialize()
    MainColor = 1   'Xanh lá
    Call GetColors
End Sub

Friend Property Get AccentClr() As Long: AccentClr = GetColor(1): End Property
Friend Property Get NormalClr() As Long: NormalClr = GetColor(0): End Property

Friend Sub ChangeTone(ByRef bytTheme As Byte)
    If MainColor <> bytTheme Then
        MainColor = bytTheme
        Call GetColors
        RaiseEvent ChangeColor(CLng(arrColors(1)), CLng(arrColors(0)))
    End If
End Sub
Private Function GetColor(ByRef bytIndex As Byte) As Long
    GetColor = arrColors(bytIndex)
End Function
Private Sub GetColors()
    Select Case MainColor        'Normal   Accent
        Case 0: arrColors = Array(vbWhite, 10158219)    'Tím
        Case 1: arrColors = Array(vbWhite, 5287936)     'Xanh lá
        Case 2: arrColors = Array(vbWhite, 9471)        'Do tuoi
        Case 3: arrColors = Array(vbWhite, 49151)       'Hô phách
    End Select
End Sub
Tải file mẫu :

Như vậy, theo cách này, chỉ những đối tượng đang mở mới bắt được sự kiện thay đổi tone màu, và do đó tài nguyên hệ thống không bị sử dụng vô tội vạ. Mặt khác, theo phương pháp lập trình thủ tục chúng ta có thể dễ dàng tác động vào các biến, hàm => Có thể xảy ra những tình huống chưa lường trước được. Ngược lại, theo phương pháp lập trình hướng đối tương, những thuộc tính và phương thức được đóng gói trong lớp nên sẽ an toàn hơn so với phương pháp trên.

Cách tổng quát để khai báo sự kiện của 1 lớp / đối tượng là :
PHP:
Public Event <Tên sự kiện>([ByVal | ByRef <Biến số 1>] As <Kiểu dữ liệu>],[ByVal | ByRef <Biến số 2>] As <Kiểu dữ liệu>])
Và cách để tạo 1 sự kiện là :
PHP:
RaiseEvent <Tên sự kiện>[<Danh sách các biến cần gửi giá trị ra môi trường ngoài nếu có>]
(Còn nữa ...)
 
Sửa lần cuối:

thaipv

Mod
Thành viên BQT
Bài 8: Tạo sự kiện (2/2)

Chào các bạn, tiếp tục chủ đề lập trình hướng đối tượng trong VBA, bài này tôi giới thiệu với các bạn phần 2 về chủ đề Tạo sự kiện cho lớp / đối tượng. Nếu như ở bài trước chúng ta tạo sự kiện mới hoàn toàn cho lớp thì ở bài này, chúng ta sẽ tạo những sự kiện (mới hoặc remix lại những sự kiện đã có sẵn) cho đối tượng có sẵn.

Như thường lệ, trước khi bắt đầu bài học, phần đầu sẽ là lý thuyết. Và hôm nay, tôi giới thiệu với các bạn kỹ thuật MultiCasting. Vậy multicasting là gì ? Sau khi nghiên cứu 1 số tài liệu, tôi có thể khái quát lại như sau : MultiCasting là kỹ thuật lập trình tạo ra 1 khuôn mẫu (lớp / đối tượng) để thế vai cho (nhiều) đối tượng có chung mục đích sử dụng.

Ví dụ : Trong sách Kỹ xảo lập trình với VB và Delphi, do tác giả Lê Hữu Đạt (chủ biên), NXB Giáo dục xuất bản năm 2000 có đưa ra tình huống như sau (tôi trích dẫn và biên soạn lại) : Giả sử bạn cần chặn một số phím trong 1 TextBox, chẳng hạn chỉ cho nhập nhập chữ (ghi họ và tên); có lẽ phương pháp thông thường nhất là bẫy code trong sự kiện KeyPress của TextBox để loại bỏ những phím không mong muốn bằng cách gán KeyAscii = 0. Nhưng nếu ứng dụng của bạn có nhiều Textbox cần chặn như vậy thì thật là bực mình và mất thì giờ khi phải copy cùng 1 đoạn code vào các sự kiện KeyPress của các TextBox !

Để giải quyết vấn đề này ta có thể dùng kỹ thuật multicasting. Bạn hãy tạo một class có tên là cStringBox có những đáp ứng như bạn muốn (chỉ nhận chữ). Sau khi bạn tạo class này rồi thì trỏ vào các TextBox bạn cần đáp ứng (thế vai) để chúng thừa hưởng những gì bạn đã cài trong class.

Mã nguồn lớp cStringBox như sau :
Mã:
Option Explicit

Private WithEvents txtMain_ As MSForms.TextBox

Friend Sub Casting(ByRef txtMain As MSForms.TextBox)
    Set txtMain_ = txtMain
End Sub
Private Sub txtMain__KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
    'KeyAscii: http://forums.codeguru.com/showthread.php?31330-keyascii-codes
    Select Case KeyAscii
        Case 8, 32, 65 To 90, 97 To 122             'Backspace, Space, a-z, A-Z
            'KeyAscii = KeyAscii                    '-> Do nothing
        Case Else
            KeyAscii = 0                            'Other Keys
    End Select
End Sub
Private Sub Class_Terminate()
    If Not txtMain_ Is Nothing Then Set txtMain_ = Nothing
End Sub
Và code trong UserForm như sau :
Mã:
Option Explicit

Private sbxFirstName    As cStringBox
Private sbxLastName     As cStringBox

Private Sub UserForm_Initialize()
    '// Set new and casting control for sbxFirstName
    Set sbxFirstName = New cStringBox
        sbxFirstName.Casting txtFirstName
  
    '// Set new and casting control for sbxLastName
    Set sbxLastName = New cStringBox
        sbxLastName.Casting txtLastName
End Sub
File mẫu tải về tại :

(Còn nữa ...)
 
Sửa lần cuối:
@thaipv anh có thể viết tiếp chủ đề này được không ạ, em đang học lập trình VBA và thấy bài viết rất dễ hiểu nhưng vẫn chưa viết hết, mong a sớm quay lại chủ đề này ạ!
 
Top