• Thứ Năm, 08/01/2004 10:34 (GMT+7)

    Tạo Form trong Access với lớp VBA

    Trong số báo tháng 02/2002, chúng tôi có bài viết “sử dụng lớp để thiết kế giao diện trong Access”. Bài viết tiếp theo này khai triển vấn đề với lớp gồm nhiều đối tượng điều khiển và thực hiện hầu hết các tác vụ cho một form nhập liệu tiêu biểu.

    Form nhập liệu


    Hình 1: form frmDataEntry

    Form frmDataEntry (hình 1) minh họa một form nhập liệu tiêu biểu. Các thành phần quan trọng của form nhập liệu gồm có:
    Nút ‘Add’: dùng để thêm mẩu tin mới.
    Nút ‘Delete’: dùng để xóa mẩu tin đang được hiển thị. Có một thông điệp khuyến cáo trước khi xóa mẩu tin.
    Nút ‘Show All’: xóa bỏ tất cả các điều kiện lọc trong Form và hiển thị tất cả các mẩu tin.
    Nút ‘Close’: đóng form hiện hành.
    Kiểm tra các field yêu cầu: trước khi lưu lại một mẩu tin, đảm bảo rằng không có field thông tin cần thiết nào trống. Hiển thị thông điệp nhắc nhở nếu cần thiết.
    Trình đơn thả xuống thực hiện tìm nhanh: trình đơn thả xuống cho phép người dùng di chuyển nhanh đến bất kỳ mẩu tin hiện hữu nào.

    Lớp DataEntryForm
    Bảng 1 liệt kê các thuộc tính của lớp DataEntryForm. Mỗi thuộc tính thể hiện một đối tượng điều khiển trên form nhập liệu.
    Hầu hết các thuộc tính của lớp DataEntryForm được thiết kế như là các thủ tục thuộc tính thiết lập giá trị cho các biến module riêng bên trong lớp. Việc thực hiện các thuộc tính của lớp như là các thủ tục thuộc tính thay vì các biến toàn cục cho phép thiết lập các thuộc tính sự kiện thích hợp của các đối tượng điều khiển.
    Các biến liên kết với các thuộc tính của đối tượng được khai báo với từ khóa ‘WithEvents’. Phương thức BindForm gán Access form như một thể hiện của lớp DataEntryForm.

    List 1: Lớp DataEntryForm

    ‘Properties

    Public ReqFields As New Collection

    ‘Private variables for properties

    Private WithEvents mForm As Form

    Private WithEvents mCmdShowAll As CommandButton

    Private WithEvents mCmdAdd As CommandButton

    Private WithEvents mCmdDelete As CommandButton

    Private WithEvents mCmdClose As CommandButton

    Private WithEvents mCboQuickFind As ComboBox

    Private WithEvents mTxtKeyField As TextBox

    Private mOpenArgs As Variant

    Private Sub mCboQuickFind AfterUpdate()

      Call FormFindCboAfterUpdate( _

      mCboQuickFind, mTxtKeyField, mForm, True)

    End Sub

    Private Sub mForm_AfterDelConfirm(Status As Integer)

      ‘Gọi thủ tục làm tươi (refresh) Combo-Find List

    ‘và gán giá trị cho Combo-Find

      ‘sau khi xóa mẩu tin hiện hành

      FormDeleteAfterEvent

      If Not (mCboQuickFind Is Nothing) Then

           Call FormFindCboRequery(mCboQuickFind)

      End If

    End Sub

    Private Sub mForm_AfterInsert()

      Call FormFindCboRequery(mCboQuickFind)

    End Sub

    Private Sub mForm_BeforeUpdate(Cancel As Integer)

      Dim ctrl As Control

      For Each ctrl In ReqFields

           If Not RequiredFieldCheck(ctrl, Cancel, mForm) Then

           Exit Sub

           End If

      Next

    End Sub

    Private Sub mForm_Current()

      Dim varReturn As Variant

      If Not (mCboQuickFind Is Nothing) Then

           Call FormFindCboUpdate( _

           mCboQuickFind, mTxtKeyField)

      End If

    End Sub

    Public Sub BindForm(frm As Form, strOpenArgs As String)

      Set mForm = frm

      mOpenArgs = strOpenArgs

      mForm.OnError = “[Event Procedure]”

      mForm.BeforeUpdate = “[Event Procedure]”

    End Sub

    Private Sub mForm_Error( _

           DataErr As Integer, Response As Integer)

      Call ComFormError(DataErr, Response, mForm)

    End Sub

    Private Sub mCmdShowAll_Click(

      Call FormShowAll(mForm)

    End Sub

    Private Sub mCmdAdd_Click()

      Call FormAddRecord(mForm)

    End Sub

    Private Sub mCmdDelete_Click()

      Call FormDeleteRecord(mForm)

    End Sub

    Private Sub mCmdClose_Click()

      Call ComCloseFrm(mForm)

    End Sub

     Public Property Set cboQuickFind(cboQuickFind)

      Set mCboQuickFind = cboQuickFind

      mCboQuickFind.AfterUpdate = “[Event Procedure]”

      mForm.OnCurrent = “[Event Procedure]”

      mForm.AfterInsert = “[Event Procedure]”

      mForm.AfterDelConfirm = “[Event Procedure]”

    End Property

    Public Property Set txtKey(txtKey)

      Set mTxtKeyField a txtKey

    End Property

    Public Property Set cmdAdd(cmdAdd)

      Set mCmdAdd = cmdAdd

      mCmdAdd.OnClick = “[Event Procedure]”

    End Property

    Public Property Set cmdDelete(cmdDelete)

      Set mCmdDelete = cmdDelete

      mCmdDelete.OnClick = “[Event Procedure]”

      mForm.OnDelete = “[Event Procedure]”

    End Property

    Public Property Set cmdClose(cmdClose)

      Set mCmdClose = cmdClose

      mCmdClose.OnClick = “[Event Procedure]”

    End Property

    Public Property Set cmdShowAll(cmdShowAll)

      Set mCmdShowAll = cmdShowAll

      mCmdShowAll.OnClick = “[Event Procedure]”

    End Property

    Phần lớn đoạn mã của lớp khá dễ hiểu và theo cùng khuôn mẫu cơ bản. Chúng ta hãy xét ví dụ nút ‘Add’.

    1. Tạo thủ tục Property Set để gán giá trị cho biến mCmdAdd và kích hoạt thủ tục mCmdAdd.OnClick.

    Public Property Set cmdAdd(cmdAdd)

      Set mCmdAdd = cmdAdd

      mCmdAdd.OnClick = “[Event Procedure]”

    End Property

    Ở đây cần giải thích thêm về việc gán giá trị “[Event Procedure]” cho thuộc tính mCmdAdd.OnClick. Các thủ tục sự kiện của Access sẽ không kích hoạt trừ khi Access nhận biết có một thủ tục (hoặc macro) được gắn vào một sự kiện đặc biệt. Nếu bạn viết một thủ tục sự kiện cho bất kỳ đối tượng Access nào, bạn sẽ thấy hiển thị “[Event Procedure]” (hình 2) bên cạnh thuộc tính sự kiện của đối tượng trong màn hình thiết kế form.
    Nếu vì một lý do nào đó, “[Event Procedure]” không được ghi vào thuộc tính, thì sự kiện sẽ không kích hoạt, ngay cả khi đoạn mã chương trình đã được nhập vào. Đây có vẻ như là lỗi của Access. Bạn nhập đoạn mã vào thủ tục sự kiện của một đối tượng, đôi lúc nó làm việc đôi lúc lại không. Giải pháp cho vấn đề này là gán tường minh “[Event Procedure]” cho các thủ tục sự kiện.

    2. Ghi đoạn mã sau cho sự kiện mCmdAdd_OnClick để khi nhấn vào nút ‘Add’ sẽ kích hoạt thủ tục yêu cầu.

    Private Sub mCmdAdd_Click()

      Call FormAddRecord(mForm)

    End Sub

    Trong chương trình ở đây, mCmdAdd_Click thực hiện lệnh gọi FormAddRecord, thủ tục này thực thi các câu lệnh di chuyển tới một mẩu tin mới.

    Function FormAddRecord(frm As Form)

    On Error GoTo FormAddRecord_Error

      DoCmd.GoToRecord , , A_NEWREC

    FormAddRecord_Exit:

      Exit Function

    FormAddRecord_Error:

      MsgBoxErr.Description, , “FormAddRecord”

      Resume FormAddRecord_Exit

    End Function

    Các đoạn mã cho các nút chức năng khác tương tự như ví dụ trên. Ở đây sẽ chỉ đưa ra những điểm quan trọng.

    Xóa mẩu tin
    Trước khi một mẩu tin bị xóa, Chương trình sẽ yâu cầu người dùng xác nhận trước khi thực thi. Điều này được thực hiện với thủ tục FormDeleteEvent của sự kiện mForm_Delete.

    Sub FormDeleteEvent( _

      Cancel As Integer, Optional varMsg As Variant)

      DoCmd.SetWarnings False

      If IsMissing(varMsg) Then

           VarMsg = “Do you want to permanently delete this record?”

      End If

      If MsgBox(varMsg, vbQuestion + vbYesNo, _

           “Confirm Delete”) = vbNo Then

           Cancel = True

      End If

      DoCmd.SetWarnings True

    End Sub

    Thủ tục này dùng DoCmd.SetWarnings để tắt thông báo mặc định của MS Access. Kế đó, nó hiển thị thông điệp thiết kế của ứng dụng. Sau đó chúng ta cho phép trở lại các thông điệp mặc định của Access. Lưu ý việc sử dụng từ khóa Optional trong phần khai báo tham số varMsg. Bạn có thể sử dụng thủ tục mà không cần phải nhập thông điệp xác nhận, trong trường hợp này thông báo mặc định sẽ hiển thị.
    Nếu người dùng chọn không xóa mẩu tin, tác vụ xóa bị hủy bỏ. Các thông báo mặc định được cho phép trở lại trong sự kiện mForm_AfterDelConfirm với thủ tục FormDeleteAfterEvent.

    Sub FormDeleteAfterEvent()

      DoCmd.SetWarnings True

    End Sub

    Tìm nhanh

    Thủ tục FormFindCboAfterUpdate được gọi với sự kiện mCboQuickFind_AfterUpdate. Thủ tục này dùng kỹ thuật đánh dấu bookmark thường dùng để tìm và di chuyển đến mẩu tin được chọn.

    Sub FormFindAfterUpdate(cbo As Control, _

    ctrlData As Control, frm As Form, intString As Integer)

    Dim rs As Recordset

    On Error GoTo FormFindCboSTRAfterUpdate_Error

    Set rs = frm.RecordsetClone

    If intString Then

    rs.FindFirst _

      ctrlData.ControlSource & “=’” & cbo & “’”’

    Else

    rs.FindFirst ctrlData.ControlSource & “=” & cbo

    End If

    If Not rs.NoMatch Then

    frm.Bookmark = rs.Bookmark

    Else

    MsgBox “Record Not Found! The record has either been deleted or you are viewing a filtered subset of data”

    cbo = ctrlData

    End If

    FormFindCboSTRAfterUpdate_Exit:

    Exit Sub

    FormFindCboSTRAfterUpdate_Error:

    cbo = ctrlData

    Resume FormFindCboSTRAfterUpdate_Exit

    End Sub

    Trong form, các nút di chuyển mẩu tin được hiển thị để cho phép người dùng dễ dàng di chuyển qua lại giữa các mẩu tin. Khi người dùng thao tác trên các nút di chuyển mẩu tin, thủ tục FormFindCboUpdate được gọi ở sự kiện mForm_Current để đồng bộ trình đơn thả xuống tìm nhanh với mẩu tin hiện hành.

    Sub FormFindCboUpdate(cbo As Control, ctrlID As Control)

      cbo = ctrlID

    End Sub

    Cuối cùng, danh sách hiển thị trong trình đơn thả xuống tìm nhanh phải được truy vấn lại khi có một mẩu tin bị xóa đi hay một mẩu tin mới được chèn vào. Việc này được thực hiện bằng cách gọi FormFindCboRequery ở các sự kiện mForm_AfterInsert và mForm_AfterDelConfirm.

    Sub FormFindCboRequery(cbo As Control)

      Cbo.Requery

    End Sub

    Hiển thị tất cả mẩu tin và đóng form

    Dưới đây là đoạn mã lệnh thực hiện đóng form và hiển thị tất cả các mẩu tin.

    Function ComCloseFrm(frm As Form)

    On Error GoTo Close_Error

    DoCmd.RunCommand acCmdSaveRecord

    DoCmd.Close acForm, frm.Name

    Close_Exit:

    Exit Function

    Close_Error:

    Resume Close_Exit

    End Function

    Function FormShowAl1(frm As Form)

    On Error Resume Next

    DoCmd.ShowAllRecords

    End Function

    Kiểm tra các Field yêu cầu: collections
    Tất cả các form nhập liệu đều có những field bắt buộc không được trống, ví dụ như field khóa chính. Các field này có thiết lập thuộc tính Required khi tạo bảng, Access có cơ chế kiểm tra riêng và thông báo lỗi mặc định. Tuy nhiên, với hầu hết ứng dụng thực tế, tốt hơn nên thay thông báo lỗi mặc định bằng thông báo của mình.

    Khai báo dưới đây được thực hiện trong khối lớp
    Public ReqFields As New Collection
    Collection đơn giản là một dãy tuyến tính các đối tượng. Bạn có thể thêm vào, loại bỏ, và đếm các phần tử trong collection. Sử dụng ReqFields collection trong sự kiện mForm_BeforeUpdate.

    Private Sub mForm_BeforeUpdate(Cancel As Integer)

      Dim ctrl As Control

      For Each ctrl In ReqFields

           If Not

           RequiredFieldCheck(ctrl, Cancel, mForm) Then

           Exit Sub

      Next

    End Sub

    RequiredFieldCheck kiểm tra đối tượng, và nếu nó trống thì hiển thị một thông báo lỗi  và định vị dấu nháy con trỏ tới đối tượng. Việc cập nhật sẽ không được thực hiện.

    Function RequiredFieldCheck(ctrl As Control, _

      Cancel As Integer, frm As Form)

    Dim intResponse As Integer

      If IsNull(ctrl) Then

      intResponse = MsgBox(“Required Field!”, _

      vbExclamation, frm.Caption)

      Cancel = True

      RequiredFieldCheck = False

      ctrl.SetFocus

      ElseIf Trimt(ctrl) = “” Then

      Cancel = True

      intResponse = MsgBox(“Required Field!”, _

      vbExclamation, frm.Caption)

      RequiredFieldCheck = False

      frm!ctrl.SetFocus

      Else

      Cancel = False

      RequiredFieldCheck = True

      End If

    End Function

    Sử dụng lớp DataEntryForm
    Bây giờ đến phần quan trọng – sử dụng lớp. Toàn bộ mã nguồn cho form nhập liệu được trình bày trong List 2.

    List 2 – frmDataEntry – phát triển theo phương thức lớp

    Private mThisForm As DataEntryForm

    Private Sub Form_Open(Cancel As Integer)

      Set mThisForm = New DataEntryForm

      mThisForm.BindForm Me, OpenArgs & “”

    Set mThisForm.cmdAdd = cmdAdd

    Set mThisForm.cmdClose = cmdClose

    Set mThisForm.cmdDelete = cmdDelete

    Set mThisForm.cmdShowAll = cmdShow_All

    Set mThisForm.cboQuickFind = cboQuickFind

    Set mThisForm.txtKey = txtCustomerID

    mThisForm.ReqFields.Add  txtCustomerID

    mThisForm.ReqFields.Add  txtCompanyName

    End Sub

    Đó là phần mã nguồn mà bạn cần cho mỗi form nhập liệu tiêu biểu của mình với đầy đủ các chức năng cần thiết.

    Ưu điểm của lớp so với form mẫu (template)


    Hình 2: Các thuộc tính ‘Event’ của đối tượng – Các sự kiện không có “[Event Procedure]” sẽ không kích hoạt trừ khi bạn gán thuộc tính sự kiện tường minh trong đoạn mã chương trình. 

    Những điều trình bày trên cũng có thể đạt được bằng cách dùng form template. Bạn có thể thiết kế form dữ liệu trống có sẵn các nút chức năng và các đoạn mã xử lý, sau đó sao chép và hiệu chỉnh cho từng form nhập liệu. Tuy nhiên, việc sử dụng lớp có nhiều ưu điểm so với template.

    1. Sự đơn giản trong thiết kế và chương trình: Phần lớn chương trình trong form nhập liệu đều nằm ở Form_Open event. Sử dụng lớp như trình bày ở trên, việc phát triển các form nhập liệu trở nên hết sức đơn giản, hầu như chỉ cần kéo và thả các field và thêm một vài đối tượng phụ vào màn hình thiết kế form. Với lớp, bạn có thể thiết kế nhanh chóng nhiều form nhập liệu mà chỉ phải thực hiện thêm vào một số ít mã lệnh. Trong khi với template, bạn phải thực hiện nhập mã lệnh trong nhiều event (thủ tục sự kiện), điều này dễ gây lẫn lộn và lỗi.

    2. Khả năng bảo trì: lớp làm cho các form dễ bảo trì hơn. Do khối lớp chứa toàn bộ các trình xử lý thực sự, việc hiệu chỉnh lớp có tác động đến tất cả các form.

    3. Mặc dù người dùng không nhìn thấy nhưng việc áp dụng kỹ thuật lập trình mới để đạt được giải pháp hiệu quả là một việc nên làm.

    Kết luận
    Loạt bài viết này chia sẻ với các bạn kinh nghiệm sử dụng lớp và đối tượng trong các  Access project. Hy vọng những điều đã trình bày giúp cho bạn thấy được công năng của lớp và giúp bạn cải thiện phương pháp luận lập trình của mình.

    Thanh Phong
    Access-VB-SQL Advisor 11/2001

    ID: A0203_74