Sử dụng Class Module và Kết nối dữ liệu SQL SERVER trong Access VBA

Liên hệ QC

lehongduc

Thành viên chính thức
Tham gia
14/9/06
Bài viết
88
Được thích
149
Chào các Bạn,

Hôm nay tôi muốn trao đổi với các Bạn về vấn đề "Sử dụng Class Module và Kết nối dữ liệu SQL SERVER trong Access VBA" như một giải pháp tối ưu cho các ứng dụng được thiết kế với VBA trong Microsoft Access. Trao đổi này có đính kèm file nguồn để làm ví dụ minh họa.

Để lấy một ví dụ cụ thể, ở đây giả định ta có nhu cầu thiết kế 1 ứng dụng Microsoft Access dùng để quản lý 1 danh bạ điện thoại.

Ứng dụng của chúng ta sẽ bao gồm 1 file dữ liệu và 1 file ứng dụng. Các Bạn có thể tạo File dữ liệu bằng Microsoft Access hoặc SQL SERVER. Ở đây tôi tạo file dữ liệu bằng SQL SERVER.
File dữ liệu đã được nạp trên 15.000 mẫu tin.
Khi file ứng dụng được nạp, ta sẽ cho kết nối với file dữ liệu bằng thủ tục Log-In.

Mục đích của tôi thông qua cách thiết kế trên nhằm:
+ minh họa khả năng của Access VBA có thể lập trình theo hướng đối tượng;
+ kết nối được với nguồn dữ liệu ngoài, ở đây là nguồn SQL SERVER;
+ có thể tạo được những Unbound Form nhằm đáp ứng nhu cầu truy xuất dữ liệu với nhiều người dùng qua mạng máy tính, đồng thời cải thiện được tốc độ xử lý dữ liệu.


Về Cấu trúc của file dữ liệu:
Với ứng dụng này ta chỉ cần có 1 file dữ liệu với 1 bảng dữ liệu. Tất nhiên các Bạn có thể tùy biến thêm nếu thấy cần.
- Tôi đặt tên file dữ liệu này là danhba
- Và tạo 1 bảng dữ liệu có tên là tblDanhsach, với các cột dữ liệu như sau:
+ Ten: tên của 1 người cụ thể trong danh bạ, kiểu dữ liệu Text
+ HoChulot: họ và chữ lót, kiểu dữ liệu Text
+ Gioitinh: xác định giới tính, kiểu dữ liệu Yes/No (mặc định là Nam, với giá trị là True)
+ Ngaysinh: ngày sinh, kiểu dữ liệu Date
+ Dtdd: số điện thoại di động, kiểu dữ liệu Text
+ Dtnha: số điện thoại ở nhà riêng, kiểu dữ liệu Text
+ Dtvp: số điện thoại ở văn phòng làm việc, kiểu dữ liệu Text

Với ứng dụng làm ví dụ sẽ cho ta biết cách:
1. Kết nối với nguồn dữ liệu bên ngoài MS. Access, ở đây là SQL SERVER
2. Viết 1 Class module như thế nào
3. Tạo 1 Unbound Form và gắn kết dữ liệu trên đó như thế nào

Trong bài sau tôi sẽ trình bày tiếp vào nội dung chính của chuyên đề này.
Rất mong các Bạn cùng tham gia nghiên cứu và trao đổi.

Nội dung các file đính kèm:
1. File ứng dụng MS. Access với định dạng mdb có mã nguồn
2. File SQL (Text) dùng để tạo database trên SQL SERVER cục bộ (local) nếu các Bạn muốn tạo.

Cũng xin trao đổi rõ thêm: File ứng dụng và file dữ liệu nêu trên mới chỉ là "sườn" còn "thô", để nó trở thành 1 ứng dụng hoàn chỉnh, chúng ta còn phải tinh chỉnh nhiều thứ; đó cũng chính là công việc mà tôi muốn mời các Bạn cùng tham gia trao đổi, qua đó chúng ta thu hoạch được những kiến thức căn bản chắc chắn hơn về chuyên đề này.

Tài liệu tham khảo:
Tài liệu tôi dùng để tham khảo chính để viết loạt bài này (bao gồm ứng dụng làm ví dụ) là loạt sách:
Beginning Access 2003 VBA, Beginning Access 2007 VBA
của Denise M. Gosnell

Link tải File ứng dụng minh họa, bản cập nhật ngày 14/7/2014:
http://www.mediafire.com/download/j5v854t46ozo5ck/qldanhba_150714.rar
 

File đính kèm

  • qldanhba.zip
    54.5 KB · Đọc: 547
  • CreateDanhbaDB.zip
    1.1 KB · Đọc: 676
Lần chỉnh sửa cuối:
Chào các Bạn,
Xin nói thêm về chuyện ứng dụng và dữ liệu còn "thô":

Nói chúng "thô" bởi lẽ:

1. File dữ liệu SQL SERVER chỉ mới có các bảng dữ liệu thôi. Như vậy chúng chỉ mới là chỗ để lưu dữ liệu phát sinh, chưa làm được việc xử lý dữ liệu (ta dễ thấy một phần những việc đơn giản trong việc xử lý dữ liệu này như: lưu, xóa, trích xuất thông tin, lọc thông tin).
Bản thân SQL SERVER là 1 hệ thống quản trị cơ sở dữ liệu mạnh, chứ không chỉ đơn thuần là nơi để lưu dữ liệu. Ta sẽ bàn tới cách giao nhiệm vụ xử lý dữ liệu cho cái file dữ liệu SQL SERVER đã tạo ở trên. Hiện nay việc xử lý dữ liệu vẫn còn do file ứng dụng đãm trách thông qua các câu lệnh SQL trong các module.

2. Nếu chạy file ứng dụng đang có ta sẽ thấy khi mở Form "frmContact" (dùng để cập nhật và xem dữ liệu) sẽ còn mất 1 ít thời gian mà ta có thể cảm nhận được. Mục tiêu của chúng ta là phải làm sao cho nhanh đến mức không cảm thấy phải chờ một chút nào.
Tôi đã kiểm tra thử mở form nói trên với kết nối internet qua 1 USB 3G của Viettel (loại 7.2 Mbps) trên xe hơi đang chạy: thời gian nạp xong form mất khoảng 25 giây.

Có Bạn nào tìm được lý do nào khác không?
 
Class là gì và tại sao ta nên dùng Class trong VBA?

1. Class là gì?
Class dùng để tạo ra những Object theo ý muốn của người thiết kế dữ liệu trong Access VBA. Thông qua Class ta có thể tạo ra được những Object với đầy đủ Properties, Method, Even tương tự như những Object có sẵn trong Access VBA.

Với ứng dụng mẫu đính kèm, ta thấy:
- Để quản lý đối tượng là danh sách trong danh bạ điện thoại ta tạo ra 1 Object có tên là clsDanhba, thông qua Object này ta có thể:
+ cập nhật hoặc lấy các thông tin chi tiết về từng người có trong danh bạ được lập như: Họ tên, Địa chỉ, số điện thoại, ...
+ Cũng thông qua Object này ta có thể thực hiện được việc xóa, thêm mới danh sách trong danh bạ

Xem ví dụ trong ClsDanhba trong file ứng dụng, ta tạo được 1 Object với tên là clsDanhba có đầy đủ:
+ các properties như: Danhbaid, Diachi, Dtdd, Dtnha, Dtvp
+ các method như: Delete, Save

Ảnh sau cho thấy việc ta gọi các properties hoặc method của clsDanhba:
DanhbaObject.png

2. Tại sao ta nên dùng Class trong VBA?
- Sẽ làm cho bộ mã (VBA code) của ứng dụng gọn gàng hơn:
+ Nếu không có Class, ta sẽ phải viết và lặp lại rất nhiều đoạn code giống nhau trong ứng dụng để quản lý thông tin của Danh bạ (lấy và cập nhật thông tin chi tiết, tạo mới, xóa bớt, ...), như vậy sẽ khó khăn cho việc bảo trì và làm cồng kềnh bộ mã ứng dụng, chắc chắn sẽ làm ứng dụng sử nhiều bộ nhớ máy tính hơn.
- Cũng thông qua Class, ta chỉ cần viết mã 1 lần, sau đó có thể sử dụng Object đã tạo cho nhiều ứng dụng cùng 1 nhóm (như 1 Add-in).
Các Bạn có thể thấy rằng, để thiết kế 1 ứng dụng quản lý công việc bán hàng chẳng hạn, nếu tạo ra 1 Object để quản lý danh sách khách hàng. Sau đó ta có thể gọi Object này ra để sử dụng trong nhiều phân hệ khác nhau như: phân hệ quan hệ khách hàng, phân hệ công nợ, ...
Rộng ra một chút, nếu tạo ra được 1 Object để quản lý các chứng từ nhập xuất phát sinh. Sau đó ta có thể gọi Object này ra để sử dụng trong các phân hệ như: phân hệ quản lý biến động kho hàng, phân hệ quản lý chế độ chiết khấu – khuyến mại, phân hệ quản lý công nợ phát sinh do việc mua bán hàng,...

Như vậy, ta đã viết 1 lần và sử dụng ở nhiều nơi khác nhau, mà không phải viết lại bộ mã để quản lý trong từng phân hệ của ứng dụng.

Các Bạn có để ý thấy bằng việc Bác Bill chỉ cần viết 1 lần thư viện quản lý dữ liệu ADO, ta đã có thể sử dụng thư viện ADO này trong bất kỳ ứng dụng quản trị dữ liệu nào, chỉ cần “nạp và yên tâm xài thôi”. Ta viết Class cũng nhằm như vậy.
Trong một dịp khác, chúng ta sẽ trao đổi sâu hơn về cách thiết kế một thư viện kiểu như vậy với Access VBA, còn lúc này hãy tập trung cho cái chuyên đề chính này đã.

Vậy cách thức để tạo ra 1 Class trong Access VBA như thế nào? Xin xem bài sau sẽ rõ.

Các Bạn có thể tham khảo giải thích chính thức của Bác Bill về Class ở link sau nhé: Source: http://msdn.microsoft.com/en-us/library/aa140954(v=office.10)
 
Lần chỉnh sửa cuối:
Xin các Bạn đừng bỏ tôi một mình độc thoại nhé.
Bàn ra hoặc tán vào nào các Bạn.
 
Xin các Bạn đừng bỏ tôi một mình độc thoại nhé.
Bàn ra hoặc tán vào nào các Bạn.

Cái này là Access nên ít người biết, ít người "bàn ra tán vào" là phải rồi
Sao bác không viết cái gì đó liên quan đến Excel?
 
Chào các Bạn,

Hôm qua, thông qua email gửi trực tiếp cho tôi một số Bạn đã phát hiện được 2 vấn đề trong file ứng dụng minh họa:
1. Nếu bỏ trống 1 vài chi tiết trên form nhập danh sách sẽ phát sinh lỗi và không cập nhật được.
2. Nhập vào rồi làm sao tìm, và các Bạn này muốn thêm công cụ tìm danh sách.

Tôi đã định những vấn đề trên sẽ được bổ sung dần trong quá trình chúng ta trao đổi về chuyên đề này, song nhận thấy có ít ý kiến tham gia trao đổi, nên hôm nay tôi tải lên đây file ứng dụng đã được bổ sung 2 vấn đề trên.

Xin tải file về từ link sau: http://www.mediafire.com/?ewyxee99pn6212i

Xin nói rõ thêm về những bổ sung trong file ứng dụng mới này:
1. Thay vì bổ sung thêm 1 cửa sổ tìm kiếm, tôi sử dụng ngay form fmContacts để làm việc này luôn. Khi nào cần tìm, các Bạn bấm vào nút "Nhập mới" để xóa trống các ô dữ liệu, sau đó nhập các yếu tố cần tìm vào ô tương ứng và bấm nút "Tìm kiếm"

2. Với chi tiết "Ngày sinh", một số Bạn cho rằng có nhu cầu bỏ trống khi chưa thu thập được thông tin cá nhân này. Do vậy tôi đã thay đổi Class clsDanhba với khai báo biến tương ứng thành Variant (thay vì Date như bản trước) để cho phép bỏ trống chi tiết này.

Rất mong các Bạn nào có thắc mắc gì xin cứ đăng ý kiến thảo luận lên diễn đàn cho mọi người cùng tham khảo sẽ có hiệu quả chung lớn hơn.

Chiều tối hôm nay tôi sẽ đăng tiếp bài về cách thức tạo 1 Class trong Access VBA. Mời các Bạn đón đọc và tham gia trao đổi.
 
Lần chỉnh sửa cuối:
Cách thức tạo 1 Class trong Access VBA

1. Chèn 1 Class module:
- Trong cửa sổ Database, chọn Modules và bấm nút lệnh New
- Trong cửa sổ “Microsoft Visual Basic” đã được mở ngay sau đó, bấm menu “Insert” sổ xuống và chọn mục lệnh “Class module”, 1 trang Class module được mở ra. Ta sẽ viết code trong trang Class module này để tạo ra 1 Class

2. Viết Class:
- Như bài 2 đã đề cập, ta dùng Class modules để thiết kế những Object theo ý riêng của mình. Mỗi Object như vậy sẽ có các property, method và cũng có thể có các event

Với ứng dụng ta đang sử dụng để minh họa:
+ Property: tương ứng với từng cột dữ liệu trong bảng dữ liệu
+ Method: tương ứng với các tác vụ như: lưu mới hoặc cập nhật các thay đổi trong bảng dữ liệu, xóa dòng trong bảng dữ liệu

Với Object mà ta định thiết kế để quản lý tập trung Danh bạ điện thoại (ta gán cho cái tên là clsDanhba):

- Ta sẽ có các properties chính là các cột dữ liệu trong bảng Danh sách, đó là:
+ Tên, Họ và Chữ lót, giới tính, ngày sinh, địa chỉ, số điện thoại di dộng, số điện thoại ở nhà riêng, số điện thoại ở văn phòng làm việc.
Như vậy, ta sẽ có 8 properties tương ứng của Object clsDanhba, tôi đặt tên là: Ten, Hochulot, Gioitinh, Ngaysinh, Diachi, Dtdd, Dtnha, Dtvp

- Ta cũng sẽ cần có các Method:
+ Để lưu và cập nhật dữ liệu, ở đây tôi đặt tên method này là “Save”
+ Để xóa dữ liệu, , ở đây tôi đặt tên method này là “Delete”
+ Để nạp các giá trị từ các cột trong bảng dữ liệu cho các properties của clsDanhba, ở đây tôi đặt tên method này là “PopulatePropertiesFromRecordset”.
Mục đích tạo ra method này nhằm cho nạp các giá trị của bảng dữ liệu vào các ô dữ liệu tương ứng trên form.
+ Để nạp các giá trị là giá trị từ các ô dữ liệu trên form danh sách cho các properties của clsDanhba, ở đây tôi đặt tên method này là “PopulatePropertiesFromForm”.
Mục đích tạo ra method này nhằm cho ghi lại các giá trị đã nhập trên form vào bảng dữ liệu

- Để khai báo các properties cho clsDanhba:
+ Mỗi một property ta viết 2 procedure: 1 procedure để lấy giá trị của property (Get value), và 1 procedure để gán giá trị cho property (Let value).
Xem file minh họa với clsDanhba, ta lấy ra 1 đoạn với 4 procedure:
Mã:
[COLOR=#0000cd]'Ten[/COLOR]
[COLOR=#006400]Public Property Get[/COLOR] Ten() As String
    On Error Resume Next
    Ten = strTen
[COLOR=#006400]End Property[/COLOR]
 
[COLOR=#006400]Public Property Let[/COLOR] Ten(ByVal Value As String)
    On Error Resume Next
    strTen = Value
[COLOR=#006400]End Property[/COLOR]
‘-----------------------------------------------------------------------
[COLOR=#0000cd]'HoChulot[/COLOR]
[COLOR=#006400]Public Property Get[/COLOR] HoChulot() As String
    On Error Resume Next
    HoChulot = strHochulot
[COLOR=#006400]End Property[/COLOR]
 
[COLOR=#006400]Public Property Let[/COLOR] HoChulot(ByVal Value As String)
    On Error Resume Next
    strHochulot = Value
[COLOR=#006400]End Property[/COLOR]
Từ đó ta dễ dàng rút ra nhận xét về dạng chung của 1 procedure phải không các Bạn.


- Nếu cần, trong Class module ta cũng có thể viết thêm các Event cho Object ta định quản lý, nhằm mục đích bẩy 1 sự kiện nào đó có liên quan đến Object này.
Thông thường, ta có các Event để làm nhiệm vụ nạp Class (Class_Initialize) và đóng Class (Class_Terminate) theo dạng thức như sau:
Mã:
[COLOR=#006400]Private Sub Class_Initialize()[/COLOR]
    ‘viết code của Bạn ở vùng này
[COLOR=#006400]End Sub[/COLOR] 

[COLOR=#006400]
Private Sub Class_Terminate()[/COLOR]
    ‘viết code của Bạn ở vùng này
[COLOR=#006400]End Sub[/COLOR]
Các Bạn có thể tham khảo chi tiết hướng dẫn của Bác Bill về cách viết các Event Procedure tại link sau nhé: http://msdn.microsoft.com/en-us/library/aa140935(v=office.10)

Để hiểu rõ hơn xin mời các Bạn mở file ứng dụng minh họa và xem nội dung Class module “clsDanhba” nhé.

Bài viết còn nữa, xin mời các bạn xem bài sau sẽ rõ.
 
Chỉnh sửa lần cuối bởi điều hành viên:
Xin các Bạn đừng bỏ tôi một mình độc thoại nhé.
Bàn ra hoặc tán vào nào các Bạn.

Bạn thấy việc làm của mình có ý nghĩa, tôi cũng thấy vậy bạn cứ làm đi, tôi rất ủng hộ bạn. Bài của bạn mới mở nhưng thời điểm này có khoảng 240 người đọc - Chính là lượng người quan tâm tới bài của bạn.
 
Chào các Bạn,

Trước khi tiếp tục nội dung chính của chuyên đề này, tôi xin trao đổi cùng các Bạn một số vấn đề mang tính chất "bếp núc" với file ứng dụng chúng ta đang sử dụng.

1. Vấn đề 1: Linh hoạt việc kết nối file ứng dụng với file dữ liệu bất kỳ.
Như các Bạn đã thấy trong file ứng dụng, chúng ta có thể tùy ý kết nối đến file dữ liệu SQL SERVER bất kỳ mà ta muốn. Các thủ tục kết nối dữ liệu trong file ứng dụng này hoàn toàn không cố định phải kết nối đến 1 file dữ liệu nào cả.
Để làm được điều đó, ứng dụng có 1 Procedure để kết nối đến file dữ liệu có thể tùy chọn được, như các Bạn thấy trong module "modQuanlyDulieu":
Mã:
[COLOR=#006400]Sub OpenDbConnection()[/COLOR]
'
[COLOR=#0000cd]'Co the tham khao chuoi ket noi den cac nguon du lieu khac nhau[/COLOR]
[COLOR=#0000cd]'tai dia chi sau: www.connectstring.com[/COLOR]


    On Error GoTo HandleError
    Dim vServer, vData, vUser, vPsw, vLogInDft As Boolean


    With Forms("frmLogIn")
        vLogInDft = !chkLogIn.Value
        If vLogInDft = True Then
            vServer = "mssql.quantribanhang.vn"
            vData = "danhba"
            vUser = "nhanvien1"
            vPsw = "Nv001"
        Else
            vServer = !txtServer
            vData = !txtData
            vUser = !txtUser
            vPsw = !txtPsw
        End If
    End With
    Set cnConn = New ADODB.Connection
    cnConn.Open _
        "Provider = sqloledb;" & _
        "Data Source=" & vServer & ";" & _
        "Initial Catalog=" & vData & ";" & _
        "User ID=" & vUser & ";" & _
        "Password=" & vPsw & ";"
    
    Exit Sub


HandleError:
    GeneralErrorHandler Err.Number, Err.Description, DB_QUANLY, "OpenDbConnection"
    Exit Sub


[COLOR=#006400]End Sub[/COLOR]
Đồng thời thiết kế 1 form để LogIn vào server và file dữ liệu xác định. Form này có tên là "frmLogIn".
Ngay trong procedure trên cũng đã tham chiếu đến các giá trị được người chạy ứng dụng khai báo trên Form này khi mở form Cập nhật Danh bạ (form "frmContacts").

Có Bạn đã hỏi tôi, nếu muốn kết nối đến file dữ liệu thiết kế bằng Microsoft Access có được không?
Hoàn toàn được các Bạn ạ. Chỉ cần khai báo lại đoạn sau trong procedure nêu trên:
Mã:
    cnConn.Open _
        "Provider = sqloledb;" & _
        "Data Source=" & vServer & ";" & _
        "Initial Catalog=" & vData & ";" & _
        "User ID=" & vUser & ";" & _
        "Password=" & vPsw & ";"
thành chuỗi kết nối đến dữ liệu Microsoft Access. Các Bạn có thể tra cứu chuỗi kết nối thích hợp tại trang www.connectstring.com
Trong trường hợp này, các Bạn phải chú ý sửa lại form "frmLogIn" và các đoạn code có liên quan trong procedure nêu trên cho phù hợp nhé.

Vấn đề 2. Xử lý những thông tin của Object bị bỏ trống (Null value) như thế nào?
Các giá trị bị bỏ trống nói ở đây có thể là giá trị trong các ô dữ liệu trên form "frmContacts" hoặc trong bảng dữ liệu SQL SERVER.
Để xử lý trường hợp này ứng dụng có 1 Function có tên là FixNull cũng ở bên trong module nêu trên:
Mã:
[COLOR=#006400]Function FixNull[/COLOR]([COLOR=#0000ff]varIn As Variant[/COLOR]) [COLOR=#006400]As String[/COLOR]


    If IsNull(varIn) Then
        FixNull = ""
    Else
        FixNull = varIn
    End If
    
[COLOR=#006400]End Function[/COLOR]
Và trong 2 procedure có liên quan trong Class module "clsDanhba" ứng dụng đã sử dụng Function FixNull này để khử các giá trị Null như các Bạn đã thấy:
Mã:
[COLOR=#006400]Sub PopulatePropertiesFromForm()[/COLOR]


'Lay thong tin tu Form frmContacts de gan gia tri cac thuoc tinh cho objDanhba


    On Error GoTo HandleError
    
    With Me
        .Ten = FixNull(Forms("frmContacts")!txtTen)
        .HoChulot = FixNull(Forms("frmContacts")!txtHoChulot)
        .Diachi = FixNull(Forms("frmContacts")!txtDiachi)
        .Dtdd = FixNull(Forms("frmContacts")!txtDtdd)
        .Dtnha = FixNull(Forms("frmContacts")!txtDtnha)
        .Dtvp = FixNull(Forms("frmContacts")!txtDtvp)
        If Len(Forms("frmContacts")!txtNgaysinh) > 0 Then
            .Ngaysinh = Forms("frmContacts")!txtNgaysinh
        Else
            .Ngaysinh = Null
        End If
        .Gioitinh = Forms("frmContacts")!frmGioitinh.Value
    End With
    Exit Sub


HandleError:
    GeneralErrorHandler Err.Number, Err.Description, CLS_DANHBA, "PopulatePropertiesFromForm"
    Exit Sub
 
[COLOR=#006400]End Sub[/COLOR]


Mã:
[COLOR=#006400]Sub PopulatePropertiesFromRecordset[/COLOR]([COLOR=#0000cd]rsCont As ADODB.Recordset[/COLOR])


'Lay thong tin tu Recordset rsCont de gan gia tri cac thuoc tinh cho objDanhba


    On Error GoTo HandleError
    
    With Me
        .DanhbaId = rsCont!DanhbaId
        .Ten = Trim(FixNull(rsCont!Ten))
        .HoChulot = Trim(FixNull(rsCont!HoChulot))
        .Diachi = Trim(FixNull(rsCont!Diachi))
        .Dtdd = Trim(FixNull(rsCont!Dtdd))
        .Dtnha = Trim(FixNull(rsCont!Dtnha))
        .Dtvp = Trim(FixNull(rsCont!Dtvp))
        If Not IsNull(rsCont!Ngaysinh) Then
            .Ngaysinh = rsCont!Ngaysinh
        Else
            .Ngaysinh = ""
        End If
        .Gioitinh = rsCont!Gioitinh
    End With
    Exit Sub


HandleError:
    GeneralErrorHandler Err.Number, Err.Description, CLS_DANHBA, "PopulatePropertiesFromRecordset"
    Exit Sub


[COLOR=#006400]End Sub[/COLOR]

3. Vấn đề bẩy lỗi trong các module:
Như các Bạn đã thấy, ứng dụng có 1 procedure để bẩy các lỗi có thể phát sinh khi chạy các thủ tục trong ứng dụng:
Mã:
[COLOR=#006400]Public Sub GeneralErrorHandler[/COLOR]([COLOR=#0000cd]lngErrNumber As Long, strErrDesc As String, strModuleSource As String, strProcedureSource As String[/COLOR])


    On Error Resume Next
    
    Dim strMessage As String
    
    'build the error message string from the parameters passed in
    strMessage = "An error has occurred in the application."
    strMessage = strMessage & vbCrLf & "Error Number: " & lngErrNumber
    strMessage = strMessage & vbCrLf & "Error Description: " & strErrDesc
    strMessage = strMessage & vbCrLf & "Module Source: " & strModuleSource
    strMessage = strMessage & vbCrLf & "Procedure Source: " & strProcedureSource
    
    'display the message to the user
    MsgBox strMessage, vbCritical
    
    Exit Sub


[COLOR=#006400]End Sub[/COLOR]
Và trong các procedure viết trong ứng dụng, đều có khai báo dòng bẩy lỗi tham chiếu đến procedure GeneralErrorHandler nêu trên:
Mã:
...
On Error GoTo HandleError
...
HandleError:
    GeneralErrorHandler Err.Number, Err.Description, <[COLOR=#0000cd]Tên module[/COLOR]>, <[COLOR=#0000cd]Tên procedure[/COLOR]>
    Exit Sub
Với cách làm như vậy, chúng ta sẽ dễ dàng quản lý được lỗi phát sinh, thậm chí xác định chính xác lỗi phát sinh ở procedure nào nằm trong module nào.

Tạm thời xin trao đổi với các Bạn 3 chuyện bếp núc như vậy. Mời các Bạn cho thêm ý kiến nhé.
 
Chào các Bạn,

Xin trao đổi thêm một chuyện bếp núc nữa mà rất đông các Bạn khi mới sử dụng Access VBA để thực hiện các câu lệnh SQL hay mắc phải đó là:
Vấn đề 4: Cập nhật chuỗi Unicode: Với file ứng dụng minh hoạ, trong module "modQuanlyDulieu" tại procedure "BuildSQLInsertDanhba" ta thấy có đoạn code sau:
Mã:
...
    strSQLInsert = "INSERT INTO " & sChemaName & ".tblDanhsach(ten,hochulot, diachi,dtdd, dtnha, dtvp,ngaysinh, gioitinh)"
    strSQLInsert = strSQLInsert & " VALUES ("
    strSQLInsert = strSQLInsert & "[COLOR=#ff0000]N[/COLOR][B][COLOR=#0000cd]'[/COLOR][/B]" & objDanhba.Ten & "[B][COLOR=#0000cd]'[/COLOR][/B], "
    strSQLInsert = strSQLInsert & "[COLOR=#ff0000]N[/COLOR][B][COLOR=#0000cd]'[/COLOR][/B]" & objDanhba.HoChulot & "[B][COLOR=#0000cd]'[/COLOR][/B], "
    strSQLInsert = strSQLInsert & "[COLOR=#ff0000]N[/COLOR][B][COLOR=#0000cd]'[/COLOR][/B]" & objDanhba.Diachi & "[B][COLOR=#0000cd]'[/COLOR][/B], "
    strSQLInsert = strSQLInsert & "'" & objDanhba.Dtdd & "', "
    strSQLInsert = strSQLInsert & "'" & objDanhba.Dtnha & "', "
    strSQLInsert = strSQLInsert & "'" & objDanhba.Dtvp & "', "
...
Trong đoạn code nêu trên các Bạn chú ý ký tự N màu đỏ đặt trước các biến chuỗi khi cho ghép thành câu lệnh SQL. Đó chính là quy ước để cập nhật chuỗi unicode trong trường hợp ta đang bàn đến.
Nếu không có ký tự N đặt trước chuỗi, chuỗi unicode sẽ được lưu thành chuỗi thường, và khi lấy giá trị các chuỗi đó ra từ bảng dữ liệu ta sẽ có chuỗi không còn dấu tiếng Việt đầy đủ nữa (vì giá trị được lưu vào không còn là Unicode).

Xin chú ý: N phải đặt trước dấu nháy trên rồi mới tới chuỗi unicode nhé. Các Bạn xem lại câu lệnh đã được phóng to lên cho dễ thấy dấu nháy trên ngay sau ký tự N nhé:

Mã:
[SIZE=5]strSQLInsert = strSQLInsert & "[COLOR=#FF0000]N[/COLOR][B][COLOR=#0000CD]'[/COLOR][/B]" & objDanhba.Ten & "[B][COLOR=#0000CD]'[/COLOR][/B], "[/SIZE]
 
Lần chỉnh sửa cuối:
Chào các Bạn,

Hôm nay xin tiếp tục trao đổi cùng các Bạn về nội dung chính của chuyên đề này:

Khai báo biến đối tượng để sử dụng class đã tạo như thế nào?

Rất đơn giản, ta khai báo biến đối tượng và sau đó gán biến đối tượng đã khai báo là 1 thành phần mới của class như đoạn code dưới đây:
Mã:
Dim objDanhba As clsDanhba
Set objDanhba = [COLOR=#ff0000]New[/COLOR] clsDanhba
...
Khi khai báo và gán biến đối tượng ta phải chú ý "câu thần chú sau: mở ra xài rồi phải đóng lại", nghĩa là: khi không còn nhu cầu sử dụng biến đối tượng đã khai báo và đã gán nữa thì ta phải cho đóng lại theo cách tương tự như đoạn code bên dưới:
Mã:
...
    rsDanhba.Close
    Set rsDanhba = Nothing
...
Câu lệnh đầu "rsDanhba.Close" có tác dụng đóng Class clsDanhba lại, câu lệnh thứ hai "Set rsDanhba = Nothing" có tác dụng xoá biến đối tượng đã gán. "Đóng" và "Xoá" ở đây để giải phóng bộ nhớ máy tính đã được cấp phát để quản lý đối tượng ta đã khai báo trước đó. Như vậy là các Bạn đã rõ việc này có vai trọng như thế nào rồi phải không.

Vấn đề ở đây là phải xác định đúng "khi nào cần dùng" và "khi nào không cần dùng nữa" để "mở" và "đóng" đúng lúc.
Xin dẫn ra đây ví dụ cụ thể ngay trong file ứng dụng minh hoạ chúng ta đang dùng:

Với form "frmContacts", do mục đích sử dụng form này là để cập nhật và trình bày các thông tin chi tiết của danh bạ nên ta sẽ cần phải dùng đến đối tượng ta đã tạo trong class "clsDanhba", vì vậy:

+ Khi form này được nạp lên màn hình, ta phải gán biến đối tượng danh bạ đã tạo ngay tại sự kiện "Form được nạp - Load_Event với thủ tục Form_Load".
Đồng thời ta cũng nhận thấy rằng biến đối tượng này ta sẽ phải sử dụng đến từ lúc form này được mở (khi cần dùng đến) cho đến lúc đóng nó lại (khi không cần dùng nữa), nên ta sẽ khai báo biến đối tượng này là biến dùng chung cho tất cả các thủ tục (procedure) có trong form,
Và chỉ đóng nó lại khi ta đóng form lại (sự kiện Unload_Event với thủ tục Form_Unload).

Vậy ta khai báo biến đối tượng này ở đâu? Ở trong từng thủ tục bên trong form "frmContacts" chăng?

Các Bạn xem trang code của form "frmContacts" sẽ thấy các biến dùng chung trong "nội bộ" form này được khai báo ở đầu trang code, các dòng khai báo này đều nằm bên ngoài các thủ tục (procedure) như đoạn code được trích bên dưới đây.

Nếu khai báo trong từng thủ tục sẽ không đạt được nhu cầu sử dụng biến đối tượng ta đã nêu ở trên (xét trong trường hợp cụ thể là file ứng dụng minh hoạ mà chúng ta đang dùng), vì khi thủ tục sự kiện hoàn tất (nghĩa là sự kiện đã hoàn thành) các biến đã khai báo và được gán sẽ bị đóng lại, các thủ tục khác không thể dùng (kế thừa) chúng được.

Mã:
Option Compare Database
Option Explicit

Dim blnAddMode As Boolean
Dim rsDanhba As ADODB.Recordset
[COLOR=#FF0000]Dim objDanhba As clsDanhba[/COLOR]
Const Danhba_FORM = "frmDanhba"
Dim intCurrDanhbaRecord As Integer
Dim rsSearch As ADODB.Recordset
Dim RecSearch As Boolean

Sáng hôm nay ta tạm thời trao đổi chừng ấy. Xin hẹn các Bạn sẽ bàn tiếp vào chiều hôm nay
 
Lần chỉnh sửa cuối:
Chào các Bạn,

Có Bạn vừa gọi hỏi tôi 2 vấn đề:

1. Có thể đổi tên class module "clsDanhba" thành tên khác (chẳng hạn như "LopDanhba") được không?

Đổi tên khác được, nhưng phải làm thêm một việc rất mất công là phải thay đổi tất cả các tham chiếu đến class module đã đổi tên. Cũng cần nói rõ hơn, nếu đổi tên "clsDanhba" thành "LopDanhba" thì khi tham chiếu đến đối tượng tương ứng cũng phải tham chiếu theo tên đã đổi.

Thí dụ:

Nếu đã khai báo biến đối tượng objDanhba bằng câu khai báo: Dim objDanhba As clsDanhba
thì cũng phải đổi dòng khai báo tên thành: Dim objDanhba As LopDanhba

Tuy nhiên, nếu Bạn muốn chuẩn hóa công việc thiết kế ứng dụng của mình khi viết code (là yêu cầu bắt buộc của làm việc khoa học), phải tuân thủ quy tắc thống nhất trong cách đặt tên biến, tên module, tên thủ tục, ... Để làm gì vậy? Để dễ nhận diện và quản lý chúng. Đừng bao giờ đặt tên tùy hứng rồi sẽ đến ngày Bạn phải trả giá rất đắt khi phải xới tung đám rừng code trong ứng dụng để tìm được đúng cái mình cần đấy.

Về cái sự "chuẩn hóa" này, như các Bạn đã thấy trong file ứng dụng minh họa, việc đặt tên đều theo 1 quy ước thông nhất đấy nhé:
+ Tên form bắt đầu bằng tiền tố "frm", như: frmContacts, frmLogIn
+ Tên module bắt đầu bằng tiền tố "mod", như: modQuanlyDulieu, modQuanlyRecord
+ Tên class module bắt đầu bằng tiền tố "cls", như: "clsDanhba"
+ Tên 1 biến đối tượng (object variabe) bắt đầu bằng tiền tố "obj", như: objDanhba
...
Bấy giờ sang vấn đề thứ hai:
2. Từ khóa "Me" trong một số thủ tục bên trong class module "clsDanhba" có ý nghĩa như thế nào? Có phải chỉ form đang mở?

Từ khóa "Me" mà các Bạn thấy ở một số thủ tục trong class module "clsDanhba" là để chỉ bản thân class mình đang mở đấy. Chằng hạn thủ tục sau bên trong class module này:
Mã:
[COLOR=#006400]Sub PopulatePropertiesFromRecordset[/COLOR]([COLOR=#0000cd]rsCont As ADODB.Recordset[/COLOR])
 'Lay thong tin tu Recordset rsCont de gan gia tri cac thuoc tinh cho objDanhba

    On Error GoTo HandleError
    
    With Me
        .DanhbaId = rsCont!DanhbaId
        .Ten = Trim(FixNull(rsCont!Ten))
        .HoChulot = Trim(FixNull(rsCont!HoChulot))
        .Diachi = Trim(FixNull(rsCont!Diachi))
        .Dtdd = Trim(FixNull(rsCont!Dtdd))
        .Dtnha = Trim(FixNull(rsCont!Dtnha))
        .Dtvp = Trim(FixNull(rsCont!Dtvp))
        If Not IsNull(rsCont!Ngaysinh) Then
            .Ngaysinh = rsCont!Ngaysinh
        Else
            .Ngaysinh = ""
        End If
        .Gioitinh = rsCont!Gioitinh
    End With
    Exit Sub

 HandleError:
    GeneralErrorHandler Err.Number, Err.Description, CLS_DANHBA, "PopulatePropertiesFromRecordset"
    Exit Sub

[COLOR=#006400]End Sub[/COLOR]
Nếu chú ý, bên trong 1 thủ tục đang viết trong class module này, khi Bạn nhập vào từ khóa "Me" với dấu "." liền ngay sau đó sẽ thấy 1 popup sổ xuống liệt kê tất cả các properties và method đã viết trong class module này (đó cũng chính là properties và method của đối tượng ta tự tạo thông qua class module đang mở)
Xem ảnh chụp màn hình sau ta sẽ thấy điều đó:
MeKeyword.jpg

Như vậy, trong class module, từ khóa "Me" không phải chỉ form đang mở các Bạn nhé.
 
Lần chỉnh sửa cuối:
Hôm nay chúng ta sẽ trao đổi tiếp tục về chuyên đề này.

Làm sao để khai báo 1 property của Object tự tạo là Read-Only (chỉ đọc mà thôi), nghĩa là ta chỉ có thể lấy được giá trị của property này, chứ không thể gán giá trị cho nó được.
Rất đơn giản, ta chỉ cần không khai báo thủ tục Property Let trong Class module là xong.
Lấy ví dụ cụ thể trong file ứng dụng minh họa mà chúng ta đang khảo sát, giả định ta muốn property DanhbaId là Read-Only, ta sẽ xóa thủ tục Public Property Let DanhbaId ra khỏi clsDanhba, chính là bỏ đi thủ tục ghi dưới đây:
Mã:
Public Property Let DanhbaId(ByVal Value As Long)
    On Error Resume Next
    lngDanhbaid = Value
End Property
Ngược lại, nếu muốn khai báo 1 property của Object tự tạo là Write-Only (chỉ ghi mà thôi), nghĩa là ta chỉ có thể gán giá trị cho property này, chứ không thể đọc giá trị của nó ra được.
Cũng tương tự như trên, ta chỉ cần không khai báo thủ tục Property Get trong Class module là xong.
Với file ứng dụng minh họa, nếu ta muốn property DanhbaId là Write-Only, ta sẽ xóa thủ tục Public Property Get DanhbaId ra khỏi clsDanhba, chính là bỏ đi thủ tục ghi dưới đây:
Mã:
Public Property Get DanhbaId() As Long
    On Error Resume Next
    DanhbaId = lngDanhbaid
End Property

Có Bạn hỏi tôi: có thể tạo ra nhiều Object trong cùng 1 class module hay không? Câu trả lời dứt khoát là không.
Mỗi Class module chỉ được dùng để tạo 1 Object thôi.

Như vậy là chúng ta đã khảo sát xong những vấn đề cơ bản về cách thức tạo 1 Object theo ý riêng thông qua công cụ Class module trong Access VBA.

Trong bài kế tiếp ta sẽ trao đổi về 1 vấn đề có liên quan là làm sao để quản lý được tất cả các thành phần riêng lẻ của 1 Object tự tạo? Kiểu như quản lý tập hợp nguyên cả cái Danh bạ, bao gồm các công việc như: thêm , xóa, đếm số lượng thành phần, ...
 
Rất mong các Bạn tham gia trao đổi về chuyên đề này. Các Bạn có thể trao đổi về:
1. Nội dung bài viết, đúng, sai?
2. Những giải pháp khác của Bạn xung quanh vấn đề chúng ta đang bàn
3. Những thắc mắc phát sinh khi chạy file ứng dụng minh họa
4. Những nhu cầu cần đáp ứng
Và những vấn đề có liên quan khác.

Khi có trao đổi qua lại, chắc chắn chúng ta sẽ có điều kiện làm sáng tỏ thêm nhiều điều và quan trọng là sẽ rút ra được những kiến thức ứng dụng thiết thực.


 
Chào các Bạn,

Hôm nay chúng ta sẽ trao đổi đến một công cụ để quản lý tập hợp các thành phần thuộc object do chúng ta tự tạo ra (bằng cách thức ta đã trao đổi trong các bài trước). Công cụ này được Access VBA gọi là Collection.

Trong Access VBA, Collection là một Object như một tập hợp các thành phần nhiều object xác định. Chẳng hạn như tập hợp từng dòng danh sách trong cùng 1 danh bạ vậy .

Collection trong Access VBA có 3 methods và 1 Property sau đây:

- Methods:
+ Add: dùng để thêm một thành phần vào Collection tự tạo. Chúng ta có thể dễ dàng truy xuất đến thành phần bất kỳ trong Collection này thông qua một khoá chỉ định, khoá này gọi là “Key”
+ Item: dùng để truy xuất đến 1 thành phần xác định thông qua 1 chỉ số index (hay là chỉ số thứ tự) của thành phần xác định đó trong Collection tự tạo
+ Remove: dùng để xoá 1 thành phần xác định khỏi Collection tự tạo thông qua chỉ số index hoặc key tương ứng của thành phần đó.

- Property:
+ Count: dùng để lấy tổng số thành phần đang có trong Collection tự tạo


Trong file ứng dụng minh họa, giả định chúng ta có nhu cầu cần xử lý một danh sách thỏa một điều kiện lọc xác định nào đó.
Cách làm khoa học nhất là sử dụng Collection để tập hợp danh sách đó lại, sau đó sẽ tùy nghi xử lý.
Với cách làm này, việc xử lý sẽ tách tập hợp danh sách này riêng ra khỏi file dữ liệu, tránh nặng nề cho các tác vụ khác trong môi trường nhiều người dùng trong mạng máy tính. Đồng thời ta cũng được lợi là danh sách (đã được lọc) ấy sẽ được lưu trữ tạm thời trong bộ nhớ máy tính (RAM) nên việc xử lý sẽ nhanh hơn.

Các Bạn xem ví dụ sau nhé:
Giả định ta muốn lấy toàn bộ danh sách đã được lọc trên form “frmContacts” để ghi vào 1 bảng dữ liệu đã được tạo trước đó (giả định bảng này tên là tblDs, với 2 cột dữ liệu: Id và Ten).
Ta sẽ phải làm 2 việc sau đây:
1. Ta viết thủ tục sau để lấy dữ liệu vào bảng tblDs, có nội dung như sau:
Mã:
[COLOR=#006400]Sub GetDataFromCollection([/COLOR][COLOR=#0000cd]strSQL As String[/COLOR][COLOR=#006400])[/COLOR]
'
Dim sqlSt As String, sCri As String, vCount As Long
Dim MyRec As ADODB.Recordset
'
[COLOR=#0000CD]Dim MyObj As clsDanhba[/COLOR]
Dim TestCol As Collection
[COLOR=#ff0000]Set TestCol = New Collection[/COLOR]
'
Set MyRec = ProcessRecordset(strSQL)
[COLOR=#0000cd]'Sau đây ta sẽ duyệt MyRec để nạp các dòng danh sách cho TestCol (Collection)[/COLOR]
Do Until MyRec.EOF
    Set MyObj = New clsDanhba
    MyObj.PopulatePropertiesFromRecordset MyRec
    TestCol.Add MyObj [COLOR=#0000cd]'Thêm thành phần vào Collection với method Add[/COLOR]
    MyRec.MoveNext
Loop
 
MyRec.Close
Set MyRec = Nothing
Set MyObj = Nothing
'
vCount = TestCol.Count [COLOR=#0000cd]'Lấy tổng số dòng danh sách (thành phần) đã thêm vào Collection với Property Count

[/COLOR]Docmd.Setwarnings False [COLOR=#0000cd]'Dòng này được bổ sung ngày 09/7/2012 để ngăn thông báo nhắc xác nhận khi chạy câu lệnh RunSQL bên dưới.[/COLOR]

DoCmd.RunSQL "DELETE * FROM tblDs"

[COLOR=#0000ff]'Sau đây ta duyệt từng thành phần trong Collection TestCol để cho ghi vào bảng tblDs
'Hoặc Bạn có thể viết code khác để làm một việc nào đó khác
'Xin chú ý cách duyệt các thành phần của 1 Collection thông qua cấu trúc câu lệnh được sơn màu đỏ bên dưới
[/COLOR][COLOR=#ff0000]For Each MyObj In TestCol[/COLOR]
    [B]With MyObj[/B]
        DoCmd.Hourglass True
        DoCmd.RunSQL "INSERT INTO tblDs(Id, Ten) VALUES(" & .DanhbaId & ",'" & .Ten & "')"
        DoCmd.Hourglass False
    [B]End With[/B]
[COLOR=#ff0000]Next[/COLOR]
Set TestCol = Nothing
 
'Open Table tblDs
MsgBox "Danh sach nay co tat ca la: " & vCount & " nguoi"
DoCmd.OpenTable "tblDs", , acReadOnly

Docmd.Setwarnings True [COLOR=#FF0000][/COLOR][COLOR=#0000cd]'Dòng này được bổ sung ngày 09/7/2012 để khôi phục lại việc cho hiện thông báo nhắc xác nhận khi chạy câu lệnh RunSQL (hoặc 1 Action-Query)[/COLOR][COLOR=#FF0000]
[/COLOR] 
End Sub
2. Ta bổ sung thêm thiết kế cho form “frmContacts”, với:
+ 1 nút lệnh có caption là “Lấy Danh sách”
+ và thêm thủ tục sự kiện Click Even cho nút lệnh này như sau:
Mã:
...
GetDataFromCollection GetSQL
'Cho hien ket qua len form
Call RunSearch
...

Bạn nào muốn xem chứ ngại viết như trên thì tải về file ứng dụng từ link sau nhé:
http://www.mediafire.com/?o5krl1bznxpanjy

Như vậy ta đã khảo sát xong 1 trường hợp sử dụng Collection.
Cũng xin các Bạn lưu ý: có rất nhiều cách sử dụng Collection. Ở đây tôi chỉ trình bày 1 cách, các Bạn có thể tùy trường hợp và nhu cầu cụ thể để sử dụng thích hợp.

Trong bài sau, chúng ta sẽ tìm hiểu vấn đề nên thiết kế Form gắn kết với nguồn dữ liệu (Bound-Form) hay thiết kế Form không gắn kết với nguồn dữ liệu (UnBound-Form).
 
Lần chỉnh sửa cuối:
Chào các Bạn,

Tôi vừa tìm lại được tài liệu + ứng dụng minh họa do tôi sưu tầm trước đây trên internet đề cập đến việc tạo và sử dụng các Object tự tạo (custom objects) và Collection tự tạo (custom collections) với Access VBA. Tất cả đều bằng tiếng Anh.
Link tải về: http://www.mediafire.com/?434diarb50ric4v

Qua tài liệu này ta sẽ thấy 1 cách khác trong việc sử dụng Collection.
 
Chào các Bạn,

Hôm nay, xin trao đổi với các Bạn về Bound Form và UnBound Form

1. Bound Form là gì?
Bound Form là 1 form được gán Record Source xác định, nghĩa là Bound Form được gán với 1 nguồn dữ liệu xác định, nguồn dữ liệu đó có thể là 1 bảng (table) hoặc 1 truy vấn dữ liệu (Query), nói theo ngôn ngữ lập trình với ADO thì nguồn dữ liệu này là 1 Recordset.
Khi tạo 1 Form trong Access, nguồn dữ liệu được khai báo thông qua property “Record Source”.
Đặc điểm của Bound Form là nguồn dữ liệu được nạp và duy trì liên tục từ lúc Form được mở cho đến khi Form được đóng lại. Mọi việc xử lý dữ liệu, từ nạp dữ liệu nguồn, duyệt dữ liệu nguồn,... hầu như đều do Access làm thay ta hết thảy.

Với các Form được đặt ở chế độ cho phép thêm, xóa, hiệu chỉnh dữ liệu trong điều kiện nhiều người sử dụng cùng lúc (truy xuất cùng 1 nguồn dữ liệu) sẽ dễ dẫn đến tình trạng khi form đang ở tình trạng hiệu chỉnh 1 mẫu tin (Record) và chưa kết thúc công việc này sẽ dẫn đến việc Access tạm khóa mẫu tin này lại (record locked) cho đến khi kết thúc việc hiệu chỉnh dữ liệu (bằng việc cho lưu các thay đổi hoặc phục hồi lại như cũ – Undo). Khi mẫu tin bị Access tạm khóa nếu lại có ai đó cũng đồng thời hiệu chỉnh mẫu tin này, Access sẽ ngăn lại và hiện thông báo cảnh báo. Nếu ta lập trình không khéo để bẩy sự kiện truy xuất trùng này sẽ dễ dàng dẫn đến làm hỏng nguồn dữ liệu đang nạp.

Các Bạn hình dung tình huống sau xem điều gì sẽ xảy ra nhé: nhân viên A đang mở mẫu tin xác định ra để hiệu chỉnh, và đang hiệu chỉnh nữa chừng chưa lưu lại các thay đổi thì bổng chột bụng cần phải “giải quyết” ngay. Thế là mẫu tin bị treo ở đó cho đến khi nhân viên A quay lại và có thao tác thích hợp. Mọi người khác phải đành bó tay khi cần làm gì đó với mẫu tin này. Lỡ nhân viên A quên quay lại để có thao tác chấm dứt cái sự nữa chừng bị treo lại kia thì sao? Bó tay đó các Bạn.

2. UnBound Form là gì?
Ngược với Bound Form, UnBound Form là form không gắn với 1 nguồn dữ liệu xác định nào cả.
Vậy làm sao UnBound Form hiển thị được thông tin ta cần đến, và làm sao để ta có thể thêm mới hoặc hiệu chỉnh nội dung một mẫu tin nào đó trong 1 nguồn dữ liệu xác định?
Nguyên tắc ở đây là: khi nào cần thì cho kết nối với dữ liệu nguồn, xong việc thì đóng kết nối lại.
- Việc kết nối với dữ liệu nguồn:
+ Có nhiều kiểu kết nối tùy theo mục đích của ta cần kết nối để làm gì? Chỉ để xem hay còn để hiệu chỉnh, cập nhật lại thông tin? Tùy theo đó mà ta lựa chọn kiểu kết nối thích hợp.
+ Mặt khác, cũng cần phải xác định rõ phạm vi kết nối, để tránh chiếm dụng vô ích tài nguyên bộ nhớ của máy tính; chẳng hạn như nếu ta chỉ cần xử lý danh sách với một phạm vi lọc nào đó (danh sách theo 1 vùng địa lý xác định được ghi trong địa chỉ của khách hàng có trong danh sách chẳng hạn), tránh việc nạp hết nguồn dữ liệu lên.
Với UnBound Form, việc truy xuất đến dữ liệu nguồn đều phải do ta tự làm lấy thông qua việc viết các thủ tục (procedure) để xử lý (từ việc nạp dữ liệu đến việc hiển thị, hiệu chỉnh, xóa, cập nhật thay đổi, ...). Đây chính là đặc điểm khác với Bound Form.

Rõ ràng, qua các đặc điểm của UnBound Form như trên đã phân tích, cho ta thấy UnBound Form thích hợp cho việc khai thác và xử lý nguồn dữ liệu trong môi trường nhiều người sử dụng cùng lúc (làm việc trên mạng máy tính), đặc biệt đối với dữ liệu có quy mô lớn (lớn về độ phức tạp và lớn về sức chứa vật lý)


3. Vậy, nên áp dụng Bound Form hay UnBound Form? Cái nào ưu việt hơn?
Câu trả lời ở đây là: lựa chọn phải tùy vào mục đích ta áp dụng để làm gì và trong hoàn cảnh cụ thể như thế nào?
Nếu chỉ với dữ liệu chạy cục bộ, chỉ có một người sử dụng trong cùng 1 thời gian và quy mô dữ liệu không lớn lắm thì lựa chọn áp dung Bound Form sẽ tốt hơn, vì dễ dàng và nhanh chóng.
Cái nào ưu việt hơn? cái nào giúp ta đạt được mục đích với chi phí ít nhất (tiền bạc, thời gian, công sức) là cái ưu việt hơn.
Ở đây chúng ta cần suy niệm nguyên tắc của tiền nhân là “đừng bao giờ mổ gà bằng dao mổ trâu và ngược lại.”

Hẹn các Bạn trong bài sau ta sẽ tìm hiểu tiếp cách thức xử lý dữ liệu với 1 Unbound Form.
 
Lần chỉnh sửa cuối:
Chào các Bạn,

Hôm nay ta tiếp tục tìm hiểu xem cách thức xử lý dữ liệu với 1 UnBound Form như thế nào?

Với 1 UnBound Form ta cần phải giải quyết những nhu cầu sau đây:
1. Làm sao để nạp được nội dung dữ liệu cho hiển thị lên các ô dữ liệu trên Form?
2. Làm sao để cập nhật các thông tin (thêm hoặc lưu các thay đổi) đang có trên Form vào nguồn dữ liệu?
3. Làm sao để xoá 1 mẫu tin xác định?
4. Làm sao để duyệt qua lại các mẫu tin của nguồn dữ liệu và cho hiển thị chúng trên Form (như 1 Bound Form đã có sẵn qua các nút lệnh duyệt tới lui qua Navigation Buttons)?

Để đáp ứng được các nhu cầu nêu trên trước hết ta cần chú ý một số vấn đề mang tính nguyên tắc trong thiết kế UnBound Form như sau:

1. Việc đặt tên các Control trong UnBound Form:
Đặc điểm của UnBound Form là không gắn với nguồn dữ liệu xác định thông qua property “Record Source”, và các Controls trong Form (TextBox, ComboBox, ListBox, …) này cũng không gắn với nguồn dữ liệu xác định thông qua property “Control Source”.

Do vậy, khi đặt tên các Controls này ta phải chú ý đặt tên sao cho thể hiện mối liên hệ trực tiếp đến các cột dữ liệu trong nguồn dữ liệu ta cần xử lý, chẳng hạn như với file ứng dụng minh hoạ, ta thấy bên trong form “frmContacts”:
+ TextBox mang tên “txtTen” sẽ được dùng để hiển thị “Tên” của từng người trong danh bạ.
+ TextBox mang tên “txtHoChulot” sẽ được dùng để hiển thị “Họ và chữ lót” của từng người trong danh bạ.

Điều đó giúp ta tránh được việc nhầm lẫn khi gán nội dung thông tin chi tiết tương ứng lên các Control này khi viết các thủ tục xử lý dữ liệu.

2. Việc cho hiển thị thông tin trên UnBound Form:
Với ứng dụng chạy trong môi trường nhiều người dùng (qua mạng máy tính) ta phải hết sức tiết kiệm tài nguyên của máy tính (bao gồm cả không gian trống của bộ nhớ và cường dộ làm việc của CPU), dù máy tính được trang bị mạnh đến cỡ nào đi nữa cũng không được lơ là việc tiết kiệm tài nguyên. Bởi vấn đề ở đây không chỉ là tiết kiệm thôi đâu, mà còn là vấn đề tránh xung đột khi xử lý dữ liệu.
Do vậy, ta chỉ cho hiển thị thông tin khi cần và chỉ nạp nguồn dữ liệu trong phạm vi vừa đúng với nhu cầu cần xử lý (không được thừa hoặc thiếu). Chẳng hạn như khi mở Form, nếu không phải là nhu cầu hiển thị kết quả tìm kiếm thì ta không nên nạp bất kỳ thông tin gì lên Form, nghĩa là để Form trống ở tình trạng sẵn sàng nhận nội dung ta nhập vào. Làm như vậy việc nạp Form lên sẽ rất nhanh, có thể nói là tức thì.

Bây giờ ta xét từng nhu cầu xử lý dữ liệu:

1. Làm sao để nạp được nội dung dữ liệu cho hiển thị lên các ô dữ liệu trên Form?
Thường để làm việc này ta cần phải qua các thủ tục sau:
+ Kết nối đến nguồn dữ liệu cần xử lý (Database)
+ Cho nạp tập hợp các mẫu tin trong phạm vi cần xử lý (Recordset)
+ Nạp thông tin chi tiết của mẫu tin đầu tiên vào các ô dữ liệu có liên quan trên form (Record)

Xét ứng dụng minh hoạ với form “frmContacts” ta thấy:
+ Để kết nối đến nguồn dữ liệu (Database) ta có thủ tục “OpenDbConnection” trong module “modQuanlyDulieu”
+ Để nạp tập hợp mẫu tin hiện ta đang có thủ tục “RetrieveDanhba” trong class module “clsDanhba” như một method của Object tự tạo “clsDanhba”.

Thủ tục “RetrieveDanhba” hiện có cho nạp tập hợp mẫu tin (Recordset) là toàn bộ bảng dữ liệu “tblDanhsach”. Ta cũng đã biết bảng “tblDanhsach” có trên 15.000 mẫu tin. Vậy là ta đã cho nạp hết trọi tập hợp trên 15.000 mẫu tin này.

Vấn đề cần được quan tâm đánh giá ở đây là: ta nạp khối lượng mẫu tin to đùng như vậy để làm gì?
Xét hết trọi các thao tác và nhu cầu hiển thị thông tin trên form “frmContacts” ta thấy:
Ngoài việc để biết bảng dữ liệu có tổng số mẫu tin là bao nhiêu, còn lại chẳng để làm gì cho có lợi cả.
Vậy thì hà cớ gì ta lại tiêu tốn một lượng lớn tài nguyên của máy tính cho chỉ duy nhất có 1 mục đích như vậy. Nếu muốn lấy tổng số mẫu tin trong 1 bảng dữ liệu ta chỉ cần “SELECT Count(*) FROM <tên_bảng_dữ_liệu>” là được rồi kia mà.

Mặt khác ta cũng thấy rằng, với kiểu xài sang đó mỗi khi mở form “frmContacts” ta thấy phải mất bộn thời gian tính bằng giây thì form mới nạp xong.

Do đó, ta cần 1 giải pháp để chỉ nạp tập hợp mẫu tin trong phạm vi cần xử lý thôi.
Sau đây là 1 cách, theo đề nghị của tôi (Nếu các Bạn có giải pháp khác xin trao đổi thêm nhé):
Ta sẽ phải làm mấy công việc sau đây:
Việc thứ nhất:
- Để bảo toàn thủ tục đang có nhằm mục đích có cái mà đối chiếu so sánh thiệt hơn giữa các giải pháp, ta sẽ viết thêm 1 thủ tục, sao cho chỉ cần nạp 1 tập hợp khoảng chừng 100 mẫu tin thôi.
Tại sao tôi lại chọn 100 mẫu tin, mà không chọn ít hơn, thậm chí chỉ cần 1 là đủ, vì mỗi lần ta chỉ hiển thị được nội dung của 1 mẫu tin lên Form “frmContacts” thôi mà?
Ái dà, cũng phải có lý do đầy đủ và hợp lý chứ các Bạn nhỉ.
Là tôi nghĩ như thế này:
Việc cần phải nạp hơn 1 mẫu tin nhằm mục đích để minh họa cho các thao tác duyệt tập hợp mẫu tin trên form “frmContacts” thông qua các nút lệnh duyệt mẫu tin tơi lui.
Do vậy, nếu nạp ít quá sẽ khó hình dung tác dụng của các thủ tục duyệt mẫu tin.
Thủ tục được viết thêm như sau:
Mã:
[COLOR=#006400]Function BuildSQLSelectLimitDanhba(FromId, ToId) As String[/COLOR]
 
    On Error GoTo HandleError
   
    Dim strSQLRetrieve As String
   
    sChemaName = GetSchemaTable("tblDanhsach")
   
    strSQLRetrieve = "SELECT * FROM " & sChemaName & ".tblDanhsach"
    strSQLRetrieve = strSQLRetrieve & " WHERE DanhbaId BETWEEN " & FromId & " AND " & ToId
    strSQLRetrieve = strSQLRetrieve & " ORDER BY DanhbaId"
   
    BuildSQLSelectLimitDanhba = strSQLRetrieve
   
    Exit Function
 
HandleError:
    GeneralErrorHandler Err.Number, Err.Description, DB_QUANLY, "BuildSQLSelectLimitDanhba"
    Exit Function
 
[COLOR=#006400]End Function[/COLOR]
Các Bạn để ý thủ tục trên sẽ thấy ta SELECT bảng dữ liệu với 1 điều kiện trong câu lệnh WHERE xác định là chỉ chọn các mẫu tin liên tục bắt đầu từ Danhbaid = biến FromId đến DanhbaId = biến ToId.
Và ta sẽ điều chỉnh lại thủ tục “RetrieveDanhba” trong class module "clsDanhba" cho thích hợp như sau:
Mã:
[COLOR=#006400]Function RetrieveDanhba(WithLimit As Boolean, Optional FromId, Optional ToId) As ADODB.Recordset[/COLOR]
 
'RetrieveDanhba: Truy xuat recordset cua Danhba thong qua cau lenh strSQLStatement
 
    On Error GoTo HandleError
    
    Dim strSQLStatement As String
    Dim rsCont As New ADODB.Recordset
 
 [COLOR=#008000]   ‘Dòng ngay bên dưới là dòng để nạp chuỗi SELECT toàn bộ danh bạ[/COLOR]
    '[COLOR=#ff0000]strSQLStatement = BuildSQLSelectDanhba[/COLOR]
 
[COLOR=#006400]    ‘==== Nay ta REM nó lại để nạp đoạn code thay thế sau đây:======[/COLOR]
    If WithLimit = True Then
        strSQLStatement = BuildSQLSelectLimitDanhba(FromId, ToId)
    Else
        strSQLStatement = BuildSQLSelectDanhba
    End If
[COLOR=#006400]    ‘============== HẾT ĐOẠN CODE MỚI ===============[/COLOR]
 
    Set rsCont = ProcessRecordset(strSQLStatement)
   
    Set RetrieveDanhba = rsCont
  
    Exit Function
 
HandleError:
    GeneralErrorHandler Err.Number, Err.Description, CLS_DANHBA, "RetrieveDanhba"
    Exit Function
 
[COLOR=#006400]End Function[/COLOR]

Việc thứ hai: là làm sao xem được tổng số mẫu tin trong bảng danh sách?
Ta phân tích nhu cầu thì thấy rằng đây là nhu cầu không cần thường xuyên, vậy ta sẽ làm việc này chỉ khi nào cần thôi.
- Tôi viết thêm thủ tục lấy tổng số mẫu tin trong bảng danh sách như sau:
+ Trước hết tôi khai báo 1 biến dùng chung cho toàn bộ ứng dụng chỉ tổng số mẫu tin trong bảng danh sách bằng câu lệnh khai báo sau:
Mã:
Public lngRecCount As Long
Tất nhiên là dòng khai báo trên nằm ở vùng Declarations của module “modQuanlyDulieu”
Và thủ tục được thêm như sau:
Mã:
[COLOR=#006400]Sub GetTotalRecCount()[/COLOR]
Dim strSQLStatement As String
Dim rsSourceRec As ADODB.Recordset
strSQLStatement = BuildSQLSelectDanhba
Set rsSourceRec = ProcessRecordset(strSQLStatement)
lngRecCount = rsSourceRec.RecordCount
Set rsSourceRec = Nothing
[COLOR=#006400]End Sub[/COLOR]
- Kế đó, tôi sẽ vẽ vời thêm vài nét trên hình hài của form “frmContacts” gồm có:
+ Thêm 1 ô kiểm (check-box) để ta đánh dấu chọn khi cần cho hiện tổng số mẫu tin của bảng dữ liệu.
+ Thêm 1 ô dữ liệu nữa để hiển thị “DanhbaId” của từng mẫu tin được nạp lên form. Ô này ta cho nó mờ đi bằng cách khai báo property “Enabled” là False (Hay “No”).
+ Thêm một nút lệnh vào nhóm các nút lệnh duyệt mẫu tin để nạp 100 mẫu tin khác khi cần và gán nó cái nhãn (caption) là “+100 Rec” cho dễ hiểu.
Cứ mỗi lần bấm nút lệnh này (Click_Event) ta sẽ cho chạy thủ tục Nạp thêm 1 tập hợp có 100 mẫu tin tiếp theo 100 mẫu tin đã nạp.
Làm sao xác định là “100 mẫu tin tiếp theo”?
Tôi chỉ cần khai báo 1 biến cục bộ trong class module của form “frmContact” (chính là cái trang code ta mở phía sau form) để chỉ DanhbaId cuối cùng của tập hợp mẫu tin đã được nạp và đang hiện hữu. Vậy là ta sẽ xác định được giá trị của 2 tham số: FromId và ToId trong thủ tục “RetrieveDanhba” nêu trên.

Thế là ta đã thỏa mãn được yêu cầu chỉ nạp tập hợp mẫu tin trong phạm vi giới hạn cần dùng.

Sau đây là Link tải xuống file ứng dụng đã cập nhật theo bài này:
http://www.mediafire.com/?c9bi76gtn2dmjv9

Đến đây bài đã dài rồi, xin hẹn các Bạn bài sau ta sẽ bàn tiếp nhé.
 
Lần chỉnh sửa cuối:
Chào các Bạn,

Tối hôm qua có Bạn gọi hỏi tôi rằng: vậy muốn nạp trở lại 100 mẫu tin trước 100 mẫu tin đang hiện hữu thì làm sao?

Các Bạn thử làm như sau xem sao nhé:
1. Thêm 1 nút lệnh với caption "-100 Rec"
2. Viết thủ tục bẩy sự kiện click_event cho nút lệnh này, trong đó xác định 2 tham số FromId và ToId như sau:
+ ToId = (Id của mẫu tin cuối cùng trong tập hợp 100 mẫu tin đang hiện hữu) - 1
+ FromId = (Id của mẫu tin cuối cùng trong tập hợp 100 mẫu tin đang hiện hữu) - 100
 
Web KT
Back
Top Bottom