Xây dựng một ứng dụng kết hợp (1 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

lethanhnhan

Thành viên chính thức
Tham gia
27/5/07
Bài viết
76
Được thích
249
Chào các bạn,
Sử dụng Excel, cũng lắm lúc các bạn cảm thấy chán ! Tại sao? Chắc có lẻ vì:
_ File Excel càng ngày lại càng lớn.
_ Xử lý thông tin với số lượng nhiều record thì lại quá lâu.
_ Bảo mật cũng...không tốt lắm.
_ Phân quyền thì cũng ... chán.
Vâng và các bạn có thể liệt kê nhiều nguyên nhân nữa.
Còn Access thì sao?
Vấn đề này đã khiến một số người dùng Access chán cái thằng cha Excel.
Vậy tại sao chúng ta không phối hợp dùng Excel và Access để tận dụng thế mạnh lẫn nhau?

Tôi xin giới thiệu giải pháp của tôi cho vấn đề này.
1. Dựa trên dữ liệu tôi cần lưu trữ tôi sẽ thiết kế file database trên Access.
2. Dùng kỹ thuật ADO để kết nối với dữ liệu.
3. Thiết kế các form nhập liệu, các thủ tục cần thiết cho công việc.
4. Các công cụ nâng cao liên quan đến cơ sở dữ liệu.

Vài bước mạn phép cùng các Lập trình viên

Đầu tiên tôi sử dụng một module để sử lý lỗi:
Mã:
Option Explicit
'Source code from Professional Excel Development
Public Const gbDEBUG_MODE As Boolean = False
Public Const glHANDLED_ERROR As Long = 9999
Public Const glUSER_CANCEL As Long = 18

Private Const msSILENT_ERROR As String = "UserCancel"
Private Const msFILE_ERROR_LOG As String = "Error.log"
Private Const gsAPP_TITLE As String = "NOTICE"

Public Function [B]bCentralErrorHandler[/B]( _
       ByVal sModule As String, _
       ByVal sProc As String, _
       Optional ByVal sFile As String, _
       Optional ByVal bEntryPoint As Boolean) As Boolean

    Static sErrMsg As String

    Dim iFile As Integer
    Dim lErrNum As Long
    Dim sFullSource As String
    Dim sPath As String
    Dim sLogText As String

    ' Grab the error info before it's cleared by
    ' On Error Resume Next below.
    lErrNum = err.Number
    ' If this is a user cancel, set the silent error flag
    ' message. This will cause the error to be ignored.
    If lErrNum = glUSER_CANCEL Then sErrMsg = msSILENT_ERROR
    ' If this is the originating error, the static error
    ' message variable will be empty. In that case, store
    ' the originating error message in the static variable.
    If Len(sErrMsg) = 0 Then
        sErrMsg = err.Description
        sErrMsg = err.Number & "| " & sErrMsg
    End If
    ' We cannot allow errors in the central error handler.
    On Error Resume Next

    ' Load the default filename if required.
    If Len(sFile) = 0 Then sFile = ThisWorkbook.Name

    ' Get the application directory.
    sPath = ThisWorkbook.Path
    If Right$(sPath, 1) <> "\" Then sPath = sPath & "\"

    ' Construct the fully-qualified error source name.
    sFullSource = "[" & sFile & "]" & sModule & "." & sProc

    ' Create the error text to be logged.
    sLogText = "  " & sFullSource & ", Error " & _
               CStr(lErrNum) & ": " & sErrMsg

    ' Open the log file, write out the error information and
    ' close the log file.
    iFile = FreeFile()
    Open sPath & msFILE_ERROR_LOG For Append As #iFile
    Print #iFile, Format$(Now(), "mm/dd/yy hh:mm:ss"); sLogText
    If bEntryPoint Then Print #iFile,
    Close #iFile

    ' Do not display silent errors.
    If sErrMsg <> msSILENT_ERROR Then

        ' Show the error message when we reach the entry point
        ' procedure or immediately if we are in debug mode.
        If bEntryPoint Or gbDEBUG_MODE Then
            Application.ScreenUpdating = True
            MsgBox sErrMsg, vbCritical, gsAPP_TITLE
            ' Clear the static error message variable once
            ' we've reached the entry point so that we're ready
            ' to handle the next error.
            sErrMsg = vbNullString
        End If

        ' The return value is the debug mode status.
        bCentralErrorHandler = gbDEBUG_MODE

    Else
        ' If this is a silent error, clear the static error
        ' message variable when we reach the entry point.
        If bEntryPoint Then sErrMsg = vbNullString
        bCentralErrorHandler = False
    End If

End Function
Tôi sẽ giới thiệu tiếp trong bài sau.

Lê Thanh Nhân
 
Dùng MZTool trợ giúp bạn

Bài 2.
Trong lập trình VBA, việc sử dụng code và chia sẻ code xem ra khó khăn.
Tôi xin giới thiệu sơ lược việc sử dụng công cụ MZTool này nhằm giúp cho công việc lập trình của bạn "dễ thở " hơn.
Đầu tiên bạn cần download MzTool cho VBA (miễn phí) từ trang:
http://www.mztools.com/index.htm
Tại đây còn có công cụ miễn phí MzTool cho Visual Basic 6.0
Khi download về các bạn cài đặt vào máy. Cứ theo hướng dẫn mà cài đặt. Sau khi công việc cài đặt kết thúc, bạn có thể dùng công cụ này trong VBE được rồi.

Mở một file Excel, nhấn tổ hợp phím Alt + F11 để vào màn hình VBE. Bạn sẽ thấy menu của công cụ này. Hoặc bạn có thể Click phải chuột vào màn hình sọan thảo code.

Menu.jpg

Shorcut_Menu.jpg


Chúng ta sẽ khám phá các thành phần cơ bản của Mztool.

Lê Thanh Nhân
 
Các công cụ của MzTool có thể liệt kê như sau:

_Find: tìm kiếm (tương tự như khi bạn nhấn tổ hợp phím Crlt + F)
_Procedure caller: gọi hàm hay thủ tục.
--------------------------------------
_Add Procedure: thêm vào procedure
_Add Code Template: thêm vào đọan mã bạn đã lưu lại
--------------------------------------
_Add Module Header: thêm vào tiêu đề cho module. Ví dụ như ngày tạo ra module. Chức năng của module, Ngày sửa, Giải thích về module này.
_Add Procedure Header: tương tự như ở trên nhưng cho Procedure.
_Add Error Hander: đưa vào bẫy lỗi.
_Add Both: đưa vào cả hai
--------------------------------------
_Add Line Numbers: nhằm giúp cho việc đọc mã được dễ dàng hơn bằng việc đánh số thứ tự vào trước mỗi hàng đọan mã của bạn.
_Remove Line Numbers: ngược lại với phần trên
--------------------------------------
_Split Lines: tách các dòng ra
_Combine Lines: kết hợp các dòng lại
--------------------------------------
_Add Current Procedure To Favourites: thêm procedure hiện tại vào Favourite
_Go To Favourite Procedure: đi đến Procedure bạn đã thêm vào ở trên
--------------------------------------
Những chức năng khác các bạn tham khảo hình vẽ.
ProcedureCaller.jpg


Các bạn có thể Click vào biểu tượng hình "cây búa và đinh vít" để sọan thảo trước khi sử dụng.
Tools.jpg

Các bạn hãy tự khám phá. Tôi chỉ xin giới thiệu một vài chức năng chính.


Lê Thanh Nhân
 
_Tab Error Hander:
Ở tab này các bạn sẽ thíêt kế mẫu bẫy lỗi mà bạn sẽ sử dụng.
TabErrorhander.jpg

Như hình trên bạn có thể thêm vào mẫu bẫy lỗi của mình.

Mã:
On Error GoTo {PROCEDURE_NAME}_Error
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = False
   End With
    {PROCEDURE_BODY}

ErrorExit:
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = True
   End With   
   Exit {PROCEDURE_TYPE}

{PROCEDURE_NAME}_Error:
If bCentralErrorHandler("{MODULE_NAME}","{PROCEDURE_NAME}" , , False) Then
     Stop
     Resume
Else
     Resume ErrorExit
End If
Tôi sẽ lần lượt nhập vào
Mã:
On Error GoTo
và click chọn combobox variables chọn Module Name, sau đó Click nút Add
bạn sẽ thấy như đọan mã sau:
Mã:
On Error GoTo {PROCEDURE_NAME}
bạn nhập tiếp "_Error", sau đó xuống hàng.
Bạn để ý PROCEDURE_NAME được bao bọc bởi {}, điều này có nghĩa chương trình sẽ thêm tự động tên của Procedure name hiện tại vào.
Trong combobox variables có các biến sau:
_Module name
_Module type
_Procedure body
_Procedure type
_Procedure Return Type Name
_Procedure Return Type Prefix
_Procedure Type
_Project Name
Sau khi bạn định nghĩa ở đây, khi cần đưa vào bẫy lỗi bạn chỉ cần Click vào toolbar button hay click phải vào menu MZTool và chọn Add Error Hander
Tương tự các bạn có thể khám phá ở các Tab khác.

Ở đây chúng ta có một Tab quan "hay hay" là tab "Code Templates"
Với tab này nó sẽ giúp ích cho bạn rất nhiều trong việc tiết kiệm thời gian viết mã.
Ở tab này bạn có thể:
_Add: thêm vào đọan mã trong "thư viện"
_Edit: sửa đọan mã trong "thư viện"
_Remove: xóa đọan mã "thư viện"
Muốn thêm đọan mã vào thư viện bạn chỉ việc click vào nút Add bên dưới. Khi bạn click Add, một form sẽ hiện ra giúp bạn thao tác để tạo ra đọan mã như: đặt tên, shortcut.
TabCodeTemplate.jpg

Bạn có thể copy và paste vào textbox text. Cuối cùng công việc bạn cần làm là click OK.
CodeTemFill.jpg


Các bạn thao tác tương tự với hai chức năng còn lại.
Một khi bạn đã thêm mã vào thư viện, bạn có thể gọi chúng ra cho các dự án khác của bạn bằng việc click phải ở màn hình VBE vào menu MZTool và chọn Add Code Template. Một form sẽ hiện ra giúp bạn chọn code template và đưa vào dự án của mình.

Lê Thanh Nhân
 
Nói thêm: MXTool sẽ lưu trữ đọan mã của bạn trong File
C:\Program Files\MZTools3VBA\MZTools3vba.ini
Các bạn có thể mở file này xem cách bố trí, lưu trữ của MZTool.
Các bạn cũng có thể xây dựng công cụ nhằm chia sẻ các đọan mã với nhau thông qua công cụ MZTool này.
Bây giờ chúng ta hãy trở về với việc xây dựng ứng dụng của mình.
Nhằm làm cho chương trình "giả lập" phân cấp người dùng chúng ta sẽ bắt buộc người dùng log-in vào mỗi khi mở file Excel. Nếu người dùng không đồng ý log-in vào thì...he he he...thóat. Với việc bắt buộc này bạn có thể phân cấp người dùng với những transaction cần thiết và ghi nhận việc thao tác với cơ sở dữ liệu. Chúng ta cũng có thể biết được ai đang truy cập vào cơ sở dữ liệu và đang thực hiện transaction gì. Việc phân cấp chúng ta sẽ để trong một bảng của Access.
Đọan mã của frmLogIn như sau:
Mã:
Option Explicit
Dim iCount As Long
Private Sub cmdLogIn_Click()
    Dim sUserName As String, sUserSoSanh As String
    Dim sPass As String, sPassSoSanh As String
    Dim sRight As String
    Dim rngRange As Range
    On Error GoTo cmdLogIn_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If iCount > 3 Then
        MsgBox "You have enter more than " & iCount & " times!" & vbCrLf & _
               "Pls, contact your Administrator.", vbOKOnly, "Inf"
        End
    End If
    sUserName = txtTenTruyCap.Text
    sUserSoSanh = UserExist(sUserName)
    'Check the user name
    If Len(Trim(sUserSoSanh)) = 0 Then
        MsgBox "This user does not exist!", vbOKOnly, "Inf"
        txtTenTruyCap.Text = ""
        txtPass.Text = ""
        txtTenTruyCap.SetFocus
        iCount = iCount + 1
    End If

    sPass = txtPass.Text
    'Get Password
    sPassSoSanh = GetUserPassword(sUserName)
    'Get right of User
    sRight = GetUserRight(sUserName)
    If sPass = sPassSoSanh And Len(Trim(sUserSoSanh)) > 0 Then
        If Len(Trim(sRight)) > 0 Then
            MsgBox "Welcome to             " & vbCrLf & _
                   "STOCK COUNT HELPER TOOL " & vbCrLf & _
                   "Author: Le Van Duyet" & vbCrLf & _
                   "You have accessed with " & sRight & " right.", vbOKOnly, "Inf"
            Application.Range("UserName").Value = sUserSoSanh
        Else
            MsgBox "Welcome to             " & vbCrLf & _
                   "STOCK COUNT HELPER TOOL " & vbCrLf & _
                   "Author: Le Van Duyet" & vbCrLf & _
                   "You have accessed with " & "?" & " right.", vbOKOnly, "Inf"
            iCount = iCount + 1
            Application.Range("UserName").Value = sUserSoSanh
        End If
    ElseIf Len(Trim(sUserSoSanh)) > 0 Then
        MsgBox "Wrong Password !" & vbCrLf & _
               "Pls enter again.", vbOKOnly, "Inf"
        txtPass.Text = ""
        txtPass.SetFocus
        iCount = iCount + 1
        Exit Sub

    End If

    If sRight = "Admin" And Len(Trim(sUserSoSanh)) > 0 Then
        'Neu quyen la admin thi se mo cac sheet
        'de cap nhat du lieu
        Call ActionB4CloseOpen("ACCESSDATA")
        Me.Hide
    ElseIf Len(Trim(sUserSoSanh)) > 0 Then
        MsgBox "You can only enter data!", vbOKOnly, "Inf"
        Call ActionB4CloseOpen("OPEN")
        Me.Hide
    End If

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

cmdLogIn_Click_Error:
    If bCentralErrorHandler("frmLogIn", "cmdLogIn_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If


End Sub

Private Sub cmdThoat_Click()
    End
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'Do not allow the user close the form by clicking the X button
    If CloseMode = vbFormControlMenu Then
        Cancel = True
    End If
End Sub
Trên form này chúng ta có hai textbox là txtPass:ghi nhận pass người dùng nhập vào. txtTenTruyCap: ghi nhận tên truy cập. Với textbox txtPass bạn chú ý thiết lập thuộc tính PassWordChar là True.
Login.jpg



Một thao tác để xử lý dữ liệu có thể nói đơn giản như sau:
_Kết nối cơ sở dữ liệu nếu được thì hiện Form giúp người dùng nhập liệu hay thao tác với dữ liệu. Nếu không sẽ thông báo và thóat. Như vậy bảo đảm rằng khi không kết nối với cơ sở dữ liệu thì người dùng sẽ không thể làm gì được.
_Bẫy lỗi trên form khi người dùng nhập dữ liệu vào. Việc bẫy lỗi này là quan trọng nhằm tránh lỗi xẫy ra khi bạn Thêm/Cập nhật hay Xóa.
_Thêm/Cập nhật/Xóa record.

Lê Thanh Nhân
 
Xây dựng các thủ tục kết nối với cơ sở dữ liệu.
Ở đây tôi xin lấy ví dụ dùng DSN trong chuỗi kết nối của connection để kết nối.
Thủ tục kết nối
Mã:
Public Sub ConnectToDatabase()
    Const msMODULE As String = "MainModule"        'Module name
    Const sSOURCE As String = "ConnectToDatabase"        ' Sub name
    Dim sConnect As String
    Dim lAttempt As Long
    Dim sSQL As String
    On Error GoTo ErrorHandler
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With    

    'Check if the connection has creased then exit sub
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        MsgBox "The connection has creased!", vbOKOnly, "Notice"
        GoTo ErrorExit
    End If

    'Create the SConnect Path
    sConnect = "DSN=WareHouseDB;UID=admin;PWD=;"
    With gcnAccess
        .Mode = adModeReadWrite
        .ConnectionTimeout = 100
        .CursorLocation = adUseClient
        .ConnectionString = sConnect
        .Open
    End With

    bConnected = True
    'Close the connection to enable connection pooling
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then gcnAccess.Close
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Sub
Chú ý rằng để sử dụng đối tượng connection cho tòan bộ dự án của bạn, bạn nên khai báo Public như đọan mã sau:
Mã:
Option Explicit

'Declare the Public variance
Public gcnAccess As New ADODB.Connection    'Global connection Access
Public bConnected As Boolean    'To let's you know the status of the connection
Thủ tục trên sẽ kết nối với cơ sở dữ liệu có DNS: WareHouseDB, với quyền Admin, không có Password.
Nếu kết nối thành công thì biến bConnected sẽ được đặt là TRUE, hoặc ngược lại. Biến này sẽ giúp ta trong việc kiểm tra kết nối trước khi thực hiện các giao dịch.
Hàm GetUserPassword dưới đây sẽ giúp bạn lấy password của người dùng khi họ nhập vào tên truy cập. Chú ý, tên truy cập và Password được đặt trong bảng tbUsers. Tương tự bạn có thể viết một hàm để lấy về quyền truy cập đối với một form, hoặc transaction.
Mã:
Public Function GetUserPassword(UserName As String) As String
    Dim rs As ADODB.Recordset
    Dim strSQL As String

    On Error GoTo GetUserPassword_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False

    End With
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    'Then get the data
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        gcnAccess.Open
        strSQL = "SELECT Pass " & _
                 "FROM tbUsers " & _
                 "WHERE User = '" & UserName & "';"
        Set rs = New ADODB.Recordset
        rs.CursorType = adOpenStatic
        rs.Open strSQL, gcnAccess

        If rs.RecordCount = 0 Then
            GetUserPassword = ""
        Else
            GetUserPassword = rs.Fields(0)
        End If
    End If

ErrorExit:
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
        bConnected = False
    End If
    If IsNull(rs) Then
        If rs.state = adStateOpen Then
            rs.Close
            Set rs = Nothing
        End If
    End If
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True

    End With
    Exit Function

GetUserPassword_Error:

    If bCentralErrorHandler("MainModule", "GetUserPassword", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Function

Lê Thanh Nhân
 
Bây giờ chúng ta hãy bắt đầu thiết kế một form nhập liệu.
NhapLieu.jpg


Trên form nhập liệu này tôi không muốn người dùng thóat bằng cách nhấn nút Close, tôi dùng đọan mã sau:
Mã:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'The purpose that Do not allow the user close the application
'by Click the X button on the Form
'Muc dich khong cho nguoi dung dong Form theo cach nay
    If CloseMode = vbFormControlMenu Then
        Cancel = True
    End If
End Sub

Mã:
Private Sub UserForm_Initialize()
Dim sDate As String
'Kiem tra xem co the connect vao file du lieu khong
'Neu khong duoc thi thong bao va Thoat
'Check the connection to the database first before doing anything

    On Error GoTo UserForm_Initialize_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    If bConnected = False Then
        MsgBox "Khong the ket noi voi Co so du lieu!" & vbCrLf & _
               "Xin ban kiem tra lai!", vbOKOnly, "Thong bao"
        End
    End If
    sDate = Format$(Day(Now()) & "/" & Month(Now()) & "/" & Year(Now()), "dd/MM/yyyy")
    txtDate.Text = sDate
    'Chu y gia tri thuoc tinh Style cua Combobox la 2-fmStyleDropDownList
    'nham chi cho phep nguoi dung chon gia tri trong Combobox nay thoi
    cbbStoreLocation.ListIndex = 0
    cbbGoodsCond.ListIndex = 0
    Call InitialTheArrayColHeader    'Khoi tao chuoi ky tu cho Column Header
    Call RangeToListView(Me.LvMaSanPham, "tbMaterialsNumber", arrColHeaderTxt)
    'Close the Connection
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

UserForm_Initialize_Error:
    If bCentralErrorHandler("frmNhapLieu", "UserForm_Initialize", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub
Ở sự kiện UserForm_Initialize, tôi sẽ kiểm tra kết nối cơ sở dữ liệu. Nếu kết nối không được sẽ thông báo vào thóat.
Trên form tôi có dùng Listview để thể hiện dữ liệu giúp người dùng nhập dữ liệu nhanh và tránh sai xót. (Dĩ nhiên tương đối thôi, nếu ngừơi dùng nhập vào 2,000 thay vì 200 thì ta cũng bó tay luôn :) )

Lê Thanh Nhân
 
Đọan mã đầy đủ của Form nhập liệu như sau:
Mã:
Option Explicit
Option Base 1
Private Sub cbbGoodsCond_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
    If KeyAscii = vbKeyReturn Then
        txtGhiChu.SetFocus
    End If
End Sub

Private Sub cmdAbout_Click()
    frmabout.Show
End Sub

Private Sub cmdIntruction_Click()
    Call ShowIntroduction
End Sub

'You need to Ref to:
'MSCOMCT.OCX, MSCOMCT2.OCX

Private Sub cmdNhap_Click()
    Dim rsData As ADODB.Recordset
    Dim rsDataCount As Long
    Dim i As Long
    Dim It As ListItem
    Dim sPeriod As String, sDate As String, sItemCode As String, sDes As String
    Dim sQty As String, sStockCardNo As String, sRemarks As String, sStoreLoc As String
    Dim sUoM As String
    Dim bUpdateOK As Boolean
    Dim iGoodsCond As Variant
    Dim sDataEntryBy As String
    Dim sLock As String
    On Error GoTo cmdNhap_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If Me.lvSanPhamNhap.ListItems.Count > 0 Then
        'Neu co san pham thi moi dua vao Co so du lieu
        sLock = GetDataLockStatus
        If UCase(sLock) = "TRUE" Then
            MsgBox "Database da bi khoa boi Administrator" & vbCrLf & _
                   "Xin cho mot ty !" & vbCrLf & _
                   "The data was locked by Administrator" & vbCrLf & _
                   "Please wait a minutes!", vbOKOnly, "Inf"
            Exit Sub
        Else
            If gcnAccess.state = ObjectStateEnum.adStateClosed Then
                Call ConnectToDatabase
                If gcnAccess.state = ObjectStateEnum.adStateClosed Then
                    gcnAccess.Open
                End If
                Set rsData = New ADODB.Recordset
                sTableInDBName = "tbMain"
                'The most common way to modify the contents of a record
                'is to open the recordset using these arguments
                'CursorType = adOpenKeySet
                'LockType   = adLockOptimistic
                rsData.Open sTableInDBName, gcnAccess, adOpenKeyset, adLockOptimistic, adCmdTable
                rsDataCount = Me.lvSanPhamNhap.ListItems.Count
                For i = 1 To rsDataCount
                    Set It = Me.lvSanPhamNhap.ListItems(i)
                    sDate = It.Text
                    With It
                        sItemCode = .SubItems(1)
                        sUoM = .SubItems(2)
                        sDes = .SubItems(3)
                        sQty = .SubItems(4)
                        sStockCardNo = .SubItems(5)
                        sRemarks = .SubItems(6)
                        sStoreLoc = .SubItems(7)
                        iGoodsCond = .SubItems(8)
                        sDataEntryBy = .SubItems(9)
                    End With
                    With rsData
                        .AddNew
                        .Fields("Date") = sDate
                        .Fields("ItemCode") = sItemCode
                        .Fields("UoM") = sUoM
                        .Fields("Descriptions") = sDes
                        .Fields("CountQty") = Val(sQty)
                        .Fields("StockCardNo") = sStockCardNo
                        .Fields("Location") = sStoreLoc
                        .Fields("Remarks") = sRemarks
                        .Fields("GoodsCondition") = iGoodsCond
                        .Fields("DataEntryBy") = sDataEntryBy
                        .Update
                    End With
                Next i
                lvSanPhamNhap.ListItems.Clear
                bUpdateOK = True
            End If
        End If
    Else
        MsgBox "Khong co gi de xuat, xin kiem lai!" & vbCrLf & _
               "There are nothing to export!" & vbCrLf & _
               "Please check !", vbOKOnly, "Inf"
    End If
    If bUpdateOK Then

        MsgBox "Xuat so lieu thanh cong!" & vbCrLf & _
               "Data was exported!", vbOKOnly, "Inf"
        txtMaSanPham.SetFocus
        txtMaSanPham.Text = IIf(bteMaterialDes = 1, txtDefTxt.Text, "")
        txtDvt.Text = ""
        txtSoTheKho.Text = ""
        txtMoTa.Text = ""
        txtGhiChu.Text = ""
    End If
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

cmdNhap_Click_Error:
    If bCentralErrorHandler("frmNhapLieu", "cmdNhap_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Private Sub cmdNhapTam_Click()
    Dim It As ListItem
    Dim sDate As String, sItemCode As String, sDes As String, sUoM As String
    Dim sQty As String, sStockCardNo As String, sRemarks As String, sStoreLoc As String
    Dim Ans As Variant, sA1 As String
    Dim iGoodsCond As Integer, sDataEntryBy As String
On Error GoTo cmdNhapTam_Click_Error
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = False
   End With
    sDate = Format$(Day(Now()) & "/" & Month(Now()) & "/" & Year(Now()), "dd/MM/yyyy")   'Get the value of the Data Picker Control
    sItemCode = txtMaSanPham.Text
    sUoM = txtDvt.Text
    sDes = txtMoTa.Text
    sQty = txtSoLuong.Text
    sStockCardNo = txtSoTheKho.Text
    sRemarks = txtGhiChu.Text
    sStoreLoc = cbbStoreLocation.Text
    iGoodsCond = IIf(cbbGoodsCond.ListIndex = 0, 1, IIf(cbbGoodsCond.ListIndex = 1, 2, 4))
    sDataEntryBy = Application.Range("UserName")
    If Len(Trim(sUoM)) = 0 Or Len(Trim(sDate)) = 0 Or _
       Len(Trim(sItemCode)) <> 10 Or Val(sQty) = 0 Or _
       Len(Trim(sStoreLoc)) = 0 Or Len(Trim(sStockCardNo)) = 0 Or _
       Len(Trim(sDataEntryBy)) = 0 Then
        'Trong truong hop can thiet
        'ban co the them vao viec kiem tra sStockCardNo
        MsgBox "Pls check your data!", vbOKOnly, "Info"
        Exit Sub
    End If
    Set It = lvSanPhamNhap.ListItems.ADD
    With It
        .Text = sDate
        .SubItems(1) = sItemCode
        .SubItems(2) = sUoM
        .SubItems(3) = sDes
        .SubItems(4) = sQty
        .SubItems(5) = sStockCardNo
        .SubItems(6) = sRemarks
        .SubItems(7) = sStoreLoc
        .SubItems(8) = iGoodsCond
        .SubItems(9) = sDataEntryBy
    End With

    txtMaSanPham.SetFocus
    txtMaSanPham.Text = IIf(bteMaterialDes = 1, txtDefTxt.Text, "")
    txtDvt.Text = ""
    txtMoTa.Text = ""
    txtSoLuong.Text = ""
    Ans = MsgBox("Do you want to keep" & vbCrLf & _
                 " Stock Card Number", vbOKCancel, "Inf")

    If Ans <> vbOK Then
        txtSoTheKho.Text = ""
        txtGhiChu.Text = ""
    End If
    

ErrorExit:
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = True
   End With
   Exit Sub

cmdNhapTam_Click_Error:
If bCentralErrorHandler("frmNhapLieu", "cmdNhapTam_Click", , False) Then
     Stop
     Resume
Else
     Resume ErrorExit
End If


End Sub

Private Sub cmdThoat_Click()
    On Error GoTo cmdThoat_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False

    End With
    'Close the Connection before Close the Form
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .StatusBar = False
    End With
    End

cmdThoat_Click_Error:
    If bCentralErrorHandler("frmNhapLieu", "cmdThoat_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If


End Sub

Private Sub LvMaSanPham_Click()
'Moi khi Click vao ListView thi
'se dua muc duoc chon vao Textbox
    On Error Resume Next
    If bteMaterialDes = 1 Then

        With Me.LvMaSanPham
            txtMaSanPham.Text = .SelectedItem.Text
            txtDvt.Text = .SelectedItem.ListSubItems(1)
            txtMoTa.Text = .SelectedItem.ListSubItems(2)
        End With
    Else
        With Me.LvMaSanPham
            txtMaSanPham.Text = .SelectedItem.ListSubItems(2)
            txtDvt.Text = .SelectedItem.ListSubItems(1)
            txtMoTa.Text = .SelectedItem.Text
        End With
    End If
    Me.LvMaSanPham.Sorted = True
End Sub

Private Sub LvMaSanPham_ColumnClick(ByVal ColumnHeader As MSComctlLib.ColumnHeader)
    If ColumnHeader.Index <> 1 And ColumnHeader.Index <> 3 Then
        frmFilter.LvMaSanPham.ListItems.Clear
        'Set the value for the array of column header text
        Call InitialTheArrayColHeader(bteMaterialDes)
        Call RangeToListView(frmFilter.LvMaSanPham, "tbMaterialsNumber", arrColHeaderTxt)
        frmFilter.Show
    Else
        bteMaterialDes = IIf(bteMaterialDes = 1, 2, 1)
        Call TransposeItemcodeAndDes(Me.LvMaSanPham)
    End If

End Sub

Private Sub LvMaSanPham_KeyUp(KeyCode As Integer, ByVal Shift As Integer)
    On Error Resume Next
    If bteMaterialDes = 1 Then
        With Me.LvMaSanPham
            txtMaSanPham.Text = .SelectedItem.Text
            txtDvt.Text = .SelectedItem.ListSubItems(1)
            txtMoTa.Text = .SelectedItem.ListSubItems(2)
        End With
    Else
        With Me.LvMaSanPham
            txtMaSanPham.Text = .SelectedItem.ListSubItems(2)
            txtDvt.Text = .SelectedItem.ListSubItems(1)
            txtMoTa.Text = .SelectedItem.Text
        End With
    End If
    If KeyCode = vbKeyReturn Then
        txtSoLuong.SetFocus
    End If
End Sub

Lê Thanh Nhân
 
Mã:
Private Sub lvSanPhamNhap_Click()
    On Error Resume Next
    If Me.lvSanPhamNhap.SelectedItem.Index <> -1 Then
        'In the case selected then action

        With Me.lvSanPhamNhap
            txtMaSanPham.Text = .SelectedItem.ListSubItems(1)
            txtDvt.Text = .SelectedItem.ListSubItems(2)
            txtMoTa.Text = .SelectedItem.ListSubItems(3)
        End With

    End If
End Sub

Private Sub lvSanPhamNhap_DblClick()
    On Error Resume Next
    If Me.lvSanPhamNhap.SelectedItem.Index <> -1 Then
        'In the case selected then action
        Me.lvSanPhamNhap.ListItems.Remove lvSanPhamNhap.SelectedItem.Index
    End If
End Sub

Private Sub txtCounter_AfterUpdate()
    txtCounter.Text = UCase(txtCounter.Text)
End Sub

Private Sub txtCounter_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
'Tuc la khong cho nhap so
    If KeyAscii >= 48 And KeyAscii <= 57 Then
        KeyAscii = 0
    End If
End Sub

Private Sub txtDefTxt_AfterUpdate()
Dim sDefText As String, sItemCode As String
sItemCode = Me.txtMaSanPham.Text
sDefText = Me.txtDefTxt.Text
If Len(sItemCode) > Len(sDefText) Then
    Me.txtMaSanPham.Text = sDefText & Mid(sItemCode, Len(sDefText) + 1, Len(sItemCode) - Len(sDefText))
Else
    Me.txtMaSanPham.Text = sDefText
End If

End Sub

Private Sub txtDefTxt_Change()
Dim sDefText As String, sItemCode As String
sItemCode = Me.txtMaSanPham.Text
sDefText = Me.txtDefTxt.Text
If Len(sItemCode) > Len(sDefText) Then
    Me.txtMaSanPham.Text = sDefText & Mid(sItemCode, Len(sDefText) + 1, Len(sItemCode) - Len(sDefText))
Else
    Me.txtMaSanPham.Text = sDefText
End If

End Sub

Private Sub txtDefTxt_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Dim sDefText As String
sDefText = Me.txtDefTxt.Text
If Len(sDefText) < 9 Then
    Select Case KeyAscii
    Case Asc("0") To Asc("9")
        'Chang lam gi ca

    Case Else
        'Cac ky tu khac thi khong cho phep nhap
        KeyAscii = 0
    End Select
Else
    KeyAscii = 0
End If
End Sub

Private Sub txtMaSanPham_AfterUpdate()
    Dim sItemCode As String, sItemCodeComparision As String
    Dim Ans As Variant
    On Error Resume Next
    sItemCode = Me.txtMaSanPham.Text

    If bteMaterialDes = 1 Then
        With Me.LvMaSanPham
            sItemCodeComparision = .SelectedItem.Text
            If sItemCode <> Left(sItemCodeComparision, Len(sItemCode)) Then
                Ans = MsgBox("Your material code is " & sItemCode & vbCrLf & _
                             "is diff from " & sItemCodeComparision & vbCrLf & _
                             "Please take note", vbOKOnly, "Inf")
                If Ans = vbOK Then
                    txtMaSanPham.Text = .SelectedItem.Text
                    txtMoTa.Text = .SelectedItem.ListSubItems(2)
                    txtDvt.Text = .SelectedItem.ListSubItems(1)
                End If
            Else
                txtMaSanPham.Text = .SelectedItem.Text
                txtMoTa.Text = .SelectedItem.ListSubItems(2)
                txtDvt.Text = .SelectedItem.ListSubItems(1)
            End If
        End With
    Else
        With Me.LvMaSanPham
            sItemCodeComparision = .SelectedItem.ListSubItems(2)
            If sItemCode <> Left(sItemCodeComparision, Len(sItemCode)) Then
                Ans = MsgBox("Your material code is " & sItemCode & vbCrLf & _
                             "is diff from " & sItemCodeComparision & vbCrLf & _
                             "Please take note", vbOKOnly, "Inf")
                If Ans = vbOK Then
                    txtMaSanPham.Text = .SelectedItem.ListSubItems(2)
                    txtDvt.Text = .SelectedItem.ListSubItems(1)
                    txtMoTa.Text = .SelectedItem.Text

                End If
            Else
                txtMaSanPham.Text = .SelectedItem.ListSubItems(2)
                txtDvt.Text = .SelectedItem.ListSubItems(1)
                txtMoTa.Text = .SelectedItem.Text

            End If
        End With
    End If
End Sub



Private Sub txtMaSanPham_Change()
    Dim sItemCode As String
    Dim It As ListItem
    On Error Resume Next
    Set It = ListViewFindItem(txtMaSanPham.Text, LvMaSanPham, elvSearchText)
End Sub

Private Sub txtMaSanPham_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
    If bteMaterialDes = 1 Then
        'Set the length of the Material Number
        If Len(txtMaSanPham) < 10 Then
            Select Case KeyAscii
            Case Asc("0") To Asc("9")
                'Chang lam gi ca/Do nothing
            Case Else
                'Cac ky tu khac thi khong cho phep nhap/Do not allow for entering other
                KeyAscii = 0
            End Select
        Else
            KeyAscii = 0
        End If
    End If
End Sub

Private Sub txtSoLuong_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
    Select Case KeyAscii
    Case Asc("0") To Asc("9")
        'Chang lam gi ca
    Case Asc("-")
        If InStr(1, Me.txtSoLuong.Text, "-") > 0 Or Me.txtSoLuong.SelStart > 0 Then
            'Neu dau tru khong nam o dau chuoi thi khong nhap
            KeyAscii = 0
        End If
    Case Asc(".")
        If InStr(1, Me.txtSoLuong.Text, ".") > 0 Then
            'Neu co dau cham thu hai thi khong nhap
            KeyAscii = 0
        End If
    Case Else
        'Cac ky tu khac thi khong cho phep nhap
        KeyAscii = 0
    End Select
End Sub

Private Sub txtSoTheKho_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
'Tuc la chi cho nhap so ma thoi
    If KeyAscii < 48 Or KeyAscii > 57 Then
        KeyAscii = 0
    End If
End Sub


Private Sub UserForm_Initialize()
Dim sDate As String
'Kiem tra xem co the connect vao file du lieu khong
'Neu khong duoc thi thong bao va Thoat
'Check the connection to the database first before doing anything

    On Error GoTo UserForm_Initialize_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    If bConnected = False Then
        MsgBox "Khong the ket noi voi Co so du lieu!" & vbCrLf & _
               "Xin ban kiem tra lai!", vbOKOnly, "Thong bao"
        End
    End If
    sDate = Format$(Day(Now()) & "/" & Month(Now()) & "/" & Year(Now()), "dd/MM/yyyy")
    txtDate.Text = sDate
    'Chu y gia tri thuoc tinh Style cua Combobox la 2-fmStyleDropDownList
    'nham chi cho phep nguoi dung chon gia tri trong Combobox nay thoi
    cbbStoreLocation.ListIndex = 0
    cbbGoodsCond.ListIndex = 0
    Call InitialTheArrayColHeader    'Khoi tao chuoi ky tu cho Column Header
    Call RangeToListView(Me.LvMaSanPham, "tbMaterialsNumber", arrColHeaderTxt)
    'Close the Connection
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

UserForm_Initialize_Error:
    If bCentralErrorHandler("frmNhapLieu", "UserForm_Initialize", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'The purpose that Do not allow the user close the application
'by Click the X button on the Form
'Muc dich khong cho nguoi dung dong Form theo cach nay
    If CloseMode = vbFormControlMenu Then
        Cancel = True
    End If
End Sub
Khi đóng form, sẽ đóng luôn kết nối, giải phóng tài nguyên.

Lê Thanh Nhân
 
Cũng như Excel, file database của Access sau một thời gian sử dụng, dung lượng cũng rất lớn bạn cần phải compact dữ liệu lại. Làm thế nào để làm từ Excel?
Các bạn hãy sử dụng đọan code sau:
Mã:
'---------------------------------------------------------------------------------------
' Procedure : CompactAccDB
' DateTime  : 13/05/2007 11:07
' Author    : levanduyet
' Purpose   : Compact the Access Database
' References: Microsoft Access 11.0 Object Library
'             C:\Program Files\Microsoft Office\OFFICE11\MSACC.OLB
'---------------------------------------------------------------------------------------
'
Public Sub CompactAccDB()
    Dim sFilePath As Variant
    Dim sOldName As String, sNewName As String
    Dim sPathOnly As String
    On Error GoTo CompactAccDB_Error

    sFilePath = Application.GetOpenFilename("Access Files,*.mdb,All Files,*.*", 1, "Pls chose the file", , False)
    If Len(sFilePath) < 6 Then
        If sFilePath = False Then
            MsgBox "You have chose Cancel.", vbOKOnly, "Inf"
        Else
            MsgBox "Pls, re-check your file!", vbOKOnly, "Inf"
            Exit Sub
        End If
    Else
        sPathOnly = PathOnly(sFilePath)
        If Right$(sPathOnly, 1) <> "\" Then sPathOnly = sPathOnly & "\"
        sOldName = sPathOnly & FileNameOnly(sFilePath)
        sNewName = sPathOnly & "DB2.mdb"
        'Start to compact
        If sOldName <> "" Then
            DoEvents 'Let other process
            DBEngine.CompactDatabase sOldName, sNewName
            Kill sOldName
            Name sNewName As sOldName
            MsgBox "You have compressed the database file", vbOKOnly, "Inf"
        End If
    End If
    Exit Sub

ErrorExit:
    MsgBox "Can not compact the file!" & vbCrLf & _
           "Database file is openning or" & vbCrLf & _
           "Check the reference to " & vbCrLf & _
           "Microsoft Access 11.0 Object Library(MSACC.OLB)", vbOKOnly, "Inf"

    Exit Sub

CompactAccDB_Error:
    If bCentralErrorHandler("DataUltilities", "CompactAccDB", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Lê Thanh Nhân
 
Vậy thì một ứng dụng của bạn trong việc kết hợp Access (hoặc các database khác) và Excel đã xong.
Bạn có thể viết một file help một cách chuyên nghiệp. Hoặc đơn giản bạn chỉ cần viết một file HuongDan.doc và đưa vào cùng thư mục với file Excel của bạn. Các bạn để ý, trong Window Explorer một khi bạn double click vào một file thì file đó sẽ mở ra đúng không? Tôi xin giới thiệu với các bạn đọan code để làm việc đó.

Mã:
Option Explicit
 
'   Khai bao nham mo mot File giong nhu khi ban Double Click vao File
'   khi muon mo mot File trong Window explorer
#If Win32 Then
    Public Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal hWnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
    Private Declare Function GetDesktopWindow Lib "user32" () As Long
 
#Else
    Declare Function ShellExecute Lib "SHELL" (ByVal hWnd%, _
                                               ByVal lpszOp$, ByVal lpszFile$, ByVal lpszParams$, _
                                               ByVal lpszDir$, ByVal fsShowCmd%) As Integer
    Declare Function GetDesktopWindow Lib "User" () As Integer
#End If
'   Bien nay se mo tap tin, xem o dang toan bo man hinh
 
Private Const SW_SHOWNORMAL = 3
 
Function StartDoc(ByVal DocName As String) As Long
'   Ham nay nham mo mot File
'   Chi viec goi ten File do
    Dim Scr_hDC As Long
    Scr_hDC = GetDesktopWindow()
    StartDoc = ShellExecute(Scr_hDC, "Open", _
                            DocName, "", "C:\", SW_SHOWNORMAL)
    'Note: If (StartDoc<0 or StartDoc>32): Open succeed
    '      Else Failed to Open
End Function
Để sử dụng trong ứng dụng của bạn, bạn tạo một nút lệnh vào gọi hàm như sau:
Mã:
Private Sub cmdIntruction_Click()
    Call ShowIntroduction
End Sub
các code liên quan
Mã:
Sub ShowIntroduction()
    Dim sFilePath As String
    Dim lX As Long
    sFilePath = GetLocalDirectory & "GioiThieu.doc"
    lX = StartDoc(sFilePath)
End Sub
Function GetLocalDirectory() As String
' Lay thu muc cua tap tin Excel dang mo
    Dim TStr
    TStr = Application.ActiveWorkbook.Path
    ' Append the backslash character if necessary
    If Right(TStr, 1) <> "\" Then TStr = TStr & "\"
    GetLocalDirectory = TStr
End Function

Lê Thanh Nhân
 
Làm cho ứng dụng của bạn có vẻ chuyên nghiệp hơn:
_Thông thường một ứng dụng có form About để giới thiệu về tác giả. Có chữ chạy lên chạy xuống, có chữ chạy ngang qua lại, có hình ảnh, có thể click rồi gởi mail cho tác giả. Các bạn hãy xem hình sau:
frmAbout.jpg


Bạn có thể tham khảo đọan code sau:

Mã:
Option Explicit
Dim iLy As Long
Dim iPic As Integer

Private Sub cmdExit_Click()
    Me.Hide
End Sub

Private Sub IMG1_Click()
    Dim iPicMax
    iPicMax = Me.ImageList1.ListImages.Count
    If iPic = iPicMax Then
        iPic = 1
    Else
        iPic = iPic + 1
    End If
    Me.IMG1.Picture = Me.ImageList1.ListImages(iPic).Picture
    Me.Repaint
End Sub

Private Sub lbltacgia_Click()
    Dim ActWin As Window
    Dim sLink As String
    On Error Resume Next
    Unload Me
    Set ActWin = ActiveWindow
    sLink = "mailto:le.van.duyet@dunlopillo.com.vn"
    ActiveWorkbook.FollowHyperlink Address:=Link, NewWindow:=True
    ActWin.Visible = True
    End
End Sub

Private Sub UserForm_Activate()
    Dim PauseTime As Variant, Start As Variant
    Dim lLen As Integer
    On Error Resume Next
Label1:
    PauseTime = 0.2    ' Set duration.
    Start = Timer    ' Set start time.
    Do While Timer < Start + PauseTime
        DoEvents    ' Yield to other processes.
    Loop
    If iLy <= -Me.lblduyet.Height Then
        iLy = Me.Height
    Else
        iLy = iLy - 2
    End If
    Me.lblduyet.Move 0, iLy
    lLen = Len(lbltacgia.Caption)
    lbltacgia.Caption = Right(lbltacgia.Caption, lLen - 1) + Left(lbltacgia.Caption, 1)
    GoTo Label1    ' Set end time.

End Sub
Private Sub UserForm_Initialize()
    Dim iLy As Integer
    Me.Caption = "About"
    iLy = Me.lblduyet.Height
    Me.IMG1.Picture = Me.ImageList1.ListImages(1).Picture
    iPic = 1
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = vbFormControlMenu Then
        Cancel = True
    End If
End Sub

_Thủ thuật để chữ chạy từ phải qua trái : cắt phần đầu chữ bên trái, dán vào bên phải của chuỗi.
_Thủ thuật để cho chữ chạy lên trên: bạn dùng phương thức Move.
_Thủ thuật để gởi mail đến tác giả:
bạn dùng thuộc tính FollowHyperlink Address:=Link, NewWindow:=True

Các bạn hãy cùng nhau khám phá thế giới của Excel nha !


Lê Thành Nhân
 
Với các bước ở trên bạn chỉ mới giúp người dùng nhập liệu mà thôi. Như thế thì ứng dụng của bạn thật là chán.
Chúng ta hãy cùng nhau xây dựng ứng dụng trích rút dữ liệu.
Các bạn hãy xem form trích rút như sau:

TrichRutDuLieu.jpg

Khi mở form chương trình sẽ tự động thể hiện thông tin bạn đã nhập vào. Ở đây chú ý rằng với kỹ thuật dùng con trỏ adUseClient công việc truy vấn sẽ nhanh hơn rất nhiều. (Thông thường chỉ xem dữ liệu thôi thì chắc có lẻ dùng kỹ thuật này thì tốt)
Mã:
With rsData
        .CursorLocation = adUseClient
        .Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
        lRecordCount = .RecordCount
End With
Đọan mã đầy đủ của form trên như sau:
Mã:
Option Explicit
Dim rsData As New ADODB.Recordset
Private Sub cbbSL_Change()
    Dim sFilter As String
    Dim lRecordCount As Long, sngQty As Single

    sFilter = cbbSL.Text
    sFilter = "Location='" & sFilter & "' "
    rsData.Filter = sFilter
    lRecordCount = rsData.RecordCount
    If lRecordCount > 0 Then
        'Update the value
        Do Until rsData.EOF
            sngQty = sngQty + Val(rsData.Fields("CountQty").Value)
            rsData.MoveNext
        Loop
        txtTotalCountQty.Text = Format(sngQty, "##,##0.00")
    Else
        txtTotalCountQty.Text = Format(0, "##,##0.00")
    End If
End Sub

Private Sub cmdExit_Click()
    On Error GoTo cmdExit_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    Set rsData = Nothing
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If
    End

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

cmdExit_Click_Error:
    If bCentralErrorHandler("frmEditData", "cmdExit_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If



End Sub

Private Sub cmdExpand_Click()

    If Me.cmdExpand.Caption = ">>" Then
        Me.cmdExpand.Caption = "<<"
        Me.Height = 450
    Else
        Me.cmdExpand.Caption = ">>"
        Me.Height = 331
    End If
End Sub

Private Sub cmdExport_Click()
    Dim lRecordCount As Long, i As Long, j As Long
    Dim rngRange As Range

    '    sSQL = "SELECT ItemCode, UoM, Descriptions, CountQty, StockCardNo, Location, " & _
         "GoodsCondition, Remarks, DataEntryBy, AutoNumber " & _
         "FROM tbMain " & _
         "ORDER BY ItemCode ; "
On Error GoTo cmdExport_Click_Error
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = False
   End With
    i = 2
    lRecordCount = rsData.RecordCount
    If lRecordCount > 0 Then
        Set rngRange = Range("tbSummaryDel")
        rngRange.ClearContents
        rsData.MoveFirst
        For j = 0 To rsData.Fields.Count - 1
            With Application.ThisWorkbook.Worksheets("SHO_SUMMARY").Range("A1")
                .Offset(1, j) = rsData.Fields(j).Name
            End With
        Next j
        Do Until rsData.EOF
            With Application.ThisWorkbook.Worksheets("SHO_SUMMARY").Range("A1")
                .Offset(i, 0) = rsData.Fields("ItemCode").Value
                .Offset(i, 1) = rsData.Fields("UoM").Value
                .Offset(i, 2) = rsData.Fields("Descriptions").Value
                .Offset(i, 3) = rsData.Fields("CountQty").Value
                .Offset(i, 4) = rsData.Fields("StockCardNo").Value
                .Offset(i, 5) = rsData.Fields("Location").Value
                .Offset(i, 6) = rsData.Fields("GoodsCondition").Value
                .Offset(i, 7) = rsData.Fields("Remarks").Value
                .Offset(i, 8) = rsData.Fields("DataEntryBy").Value
                .Offset(i, 9) = Round(rsData.Fields("AutoNumber").Value, 0)

            End With
            rsData.MoveNext
            i = i + 1
        Loop
        rngRange.Columns.AutoFit
        ' Application.ThisWorkbook.Worksheets("SUMMARY").Range("A1").Offset(1, 0).CopyFromRecordset rsData
        ' This Method can not do when the Filter was applied to recordset
    Else
        MsgBox "There are no record" & vbCrLf & _
               "Khong co item nao!", vbOKOnly, "Inf"
    End If
    Set rngRange = Nothing

ErrorExit:
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = True
   End With
   Exit Sub

cmdExport_Click_Error:
If bCentralErrorHandler("frmEditData", "cmdExport_Click", , False) Then
     Stop
     Resume
Else
     Resume ErrorExit
End If


End Sub

Private Sub cmdUpdate_Click()
    Dim sAutoNumber As String
    Dim sMaterial As String, sStockCountQty As String
    Dim sStockCardNo As String, sSLocation As String, sUoM As String, sDes As String
    Dim sGoodsCond As String
    Dim rsUpdate As New ADODB.Recordset, lrsUpdateCount As Long, sSQL As String
    Dim bUpdateSucceed As Boolean
    On Error GoTo cmdUpdate_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    sAutoNumber = txtAutoNumber.Text
    If Len(Trim(sAutoNumber)) > 0 And sAutoNumber <> "AutoNumber" Then
        'Edit the record
        'First have to check
        sMaterial = txt1.Text
        sDes = txtDes.Text
        sUoM = txtUoM.Text
        sStockCountQty = txt2.Text
        sStockCardNo = txt3.Text
        sSLocation = txt4.Text
        sGoodsCond = txt5.Text
        bUpdateSucceed = False
        'Built the sSQL
        If Len(Trim(sMaterial)) > 0 Then
            sSQL = "UPDATE tbMain " & _
                   "SET ItemCode='" & sMaterial & "' , Descriptions='" & sDes & "' , UoM='" & sUoM & "' " & _
                   "WHERE Autonumber=" & Val(sAutoNumber) & "; "
            rsUpdate.Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
            bUpdateSucceed = True
        ElseIf Len(Trim(sStockCountQty)) > 0 Then
            sSQL = "UPDATE tbMain " & _
                   "SET CountQty=" & Val(sStockCountQty) & " " & _
                   "WHERE Autonumber=" & Val(sAutoNumber) & "; "
            rsUpdate.Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
            bUpdateSucceed = True
        ElseIf Len(Trim(sStockCardNo)) > 0 Then
            sSQL = "UPDATE tbMain " & _
                   "SET StockCardNo=" & Val(sStockCardNo) & " " & _
                   "WHERE Autonumber=" & Val(sAutoNumber) & "; "
            rsUpdate.Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
            bUpdateSucceed = True
        ElseIf Len(Trim(sSLocation)) > 0 Then
            sSQL = "UPDATE tbMain " & _
                   "SET Location='" & sSLocation & "' " & _
                   "WHERE Autonumber=" & Val(sAutoNumber) & "; "
            rsUpdate.Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
            bUpdateSucceed = True
        ElseIf Len(Trim(sGoodsCond)) > 0 Then
            sSQL = "UPDATE tbMain " & _
                   "SET GoodsCondition=" & Val(sGoodsCond) & " " & _
                   "WHERE Autonumber=" & Val(sAutoNumber) & "; "
            rsUpdate.Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
            bUpdateSucceed = True
        Else
            MsgBox "There are nothing to update!", vbOKOnly, "Inf"
            Exit Sub
        End If
        If bUpdateSucceed Then
            MsgBox "You have updated.", vbOKOnly, "Inf"
            txt1.Text = ""
            txtDes.Text = ""
            txtUoM.Text = ""
            txt2.Text = ""
            txt3.Text = ""
            txt4.Text = ""
            txt5.Text = ""
            txt2.SetFocus
        End If
    End If

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

cmdUpdate_Click_Error:
    If bCentralErrorHandler("frmEditData", "cmdUpdate_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If


End Sub
Lê Thanh Nhân
 
Mã:
Private Sub MSHFlexGridDB_DblClick()
    Dim Ans As Variant
    Dim CellIndex, lRecordCount As Long, sngQty As Variant
    Dim sMaterialNumber As String, sFilter As String
On Error GoTo MSHFlexGridDB_DblClick_Error
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = False
   End With
    With Me.MSHFlexGridDB
        CellIndex = (Me.MSHFlexGridDB.RowSel) * Me.MSHFlexGridDB.Cols  '+ Me.MSHFlexGridDB.ColSel
    End With
    Ans = MsgBox("Do you want to update" & vbCrLf & _
                 "this record?", vbOKCancel, "Inf")
    'Me.Height = 395
    If Ans = vbOK Then
        
        With Me.MSHFlexGridDB
            txt12.Text = .TextArray(CellIndex + 1)
            txt22.Text = .TextArray(CellIndex + 4)
            txt32.Text = .TextArray(CellIndex + 5)
            txt42.Text = .TextArray(CellIndex + 6)
            txt52.Text = .TextArray(CellIndex + 7)
            txtAutoNumber.Text = .TextArray(CellIndex + 10)
        End With
    Else
        With Me.MSHFlexGridDB
            txt12.Text = ""
            txtUoM.Text = ""
            txt22.Text = ""
            txt32.Text = ""
            txt42.Text = ""
            txt52.Text = ""
            txtAutoNumber.Text = ""
        End With
    End If

ErrorExit:
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = True
   End With
   Exit Sub

MSHFlexGridDB_DblClick_Error:
If bCentralErrorHandler("frmEditData", "MSHFlexGridDB_DblClick", , False) Then
     Stop
     Resume
Else
     Resume ErrorExit
End If


End Sub

Private Sub txt1_AfterUpdate()
    Dim sItemCode As String, sDes As String, sUoM As String
    Dim sSQL As String, rsCheck As ADODB.Recordset
    Dim lrsCheckCount As Long

    On Error GoTo txt1_AfterUpdate_Error
    With Application
        .Calculation = xlCalculationManual
    End With
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    If bConnected = False Then
        MsgBox "Khong the ket noi voi Co so du lieu!" & vbCrLf & _
               "Xin ban kiem tra lai!" & _
               "Can not contact to Database!" & _
               "Please check!", vbOKOnly, "Inf"
    End If
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        gcnAccess.Open
    End If
    sItemCode = txt1.Text
    sSQL = "SELECT ItemCode, Descriptions, UoM " & _
           "FROM tbSAP " & _
           "WHERE ItemCode='" & sItemCode & "';"
    Set rsCheck = New ADODB.Recordset
    rsCheck.Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
    lrsCheckCount = rsCheck.RecordCount
    If lrsCheckCount = 0 Then
        MsgBox "This material number do no exist" & vbCrLf & _
               "in the material list" & vbCrLf & _
               "Pls, check!", vbOKOnly, "Inf"
        txt1.Text = ""
        txtDes.Text = ""
        txtUoM.Text = ""
    Else
        txtDes.Text = rsCheck.Fields("Descriptions").Value
        txtUoM.Text = rsCheck.Fields("UoM").Value
    End If
    Set rsCheck = Nothing

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

txt1_AfterUpdate_Error:
    If bCentralErrorHandler("frmEditData", "txt1_AfterUpdate", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If


End Sub

Private Sub txt1_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
'Set the length of the Material Number
    If Len(txt1.Text) < 10 Then
        Select Case KeyAscii
        Case Asc("0") To Asc("9")
            'Chang lam gi ca/Do nothing
        Case Else
            'Cac ky tu khac thi khong cho phep nhap/Do not allow for entering other
            KeyAscii = 0
        End Select
    Else
        KeyAscii = 0
    End If

End Sub
Private Sub txt2_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case KeyAscii
    Case Asc("0") To Asc("9")
        'Chang lam gi ca
'    Case Asc("-")
'        If InStr(1, Me.txtSoLuong.Text, "-") > 0 Or Me.txtSoLuong.SelStart > 0 Then
'            'Neu dau tru khong nam o dau chuoi thi khong nhap
'            KeyAscii = 0
'        End If
    Case Asc(".")
        If InStr(1, Me.txt2.Text, ".") > 0 Then
            'Neu co dau cham thu hai thi khong nhap
            KeyAscii = 0
        End If
    Case Else
        'Cac ky tu khac thi khong cho phep nhap
        KeyAscii = 0
    End Select
End Sub

Private Sub txt3_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
'Set the length of the Material Number
    If Len(txt2.Text) < 10 Then
        Select Case KeyAscii
        Case Asc("0") To Asc("9")
            'Chang lam gi ca/Do nothing
        Case Else
            'Cac ky tu khac thi khong cho phep nhap/Do not allow for entering other
            KeyAscii = 0
        End Select
    Else
        KeyAscii = 0
    End If

End Sub

Private Sub txt4_AfterUpdate()
    Dim sSLoc As String
    sSLoc = txt4.Text
    sSLoc = Trim(sSLoc)
    If (sSLoc <> "W001") And (sSLoc <> "P001") And (sSLoc <> "R001") Then
        txt4.Text = ""
    End If
End Sub

Private Sub txt4_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
    Dim iLen As Integer
    iLen = Len(txt4.Text)
    'Set the length of the Material Number
    If iLen < 4 Then
        Select Case iLen
        Case 0
            Select Case KeyAscii
            Case vbKeyW, vbKeyR, vbKeyP
            Case Else
                KeyAscii = 0
            End Select
        Case iLen = 1 To 2
            Select Case KeyAscii
            Case vbKey0
            Case Else
                KeyAscii = 0
            End Select
        Case 3
            Select Case KeyAscii
            Case vbKey1
            Case Else
                KeyAscii = 0
            End Select
        End Select

    Else
        KeyAscii = 0
    End If
End Sub
Lê Thanh Nhân
 
Mã:
Private Sub txt5_AfterUpdate()
    Dim sGoodsCond As String
    sGoodsCond = txt5.Text
    Select Case sGoodsCond
    Case "1"
        lbl52.Caption = "Good"
    Case "2"
        lbl52.Caption = "QI"
    Case "4"
        lbl52.Caption = "Blocked"
    Case Else
        lbl52.Caption = ""
    End Select
End Sub

Private Sub txt5_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
'Set the length of the Material Number
    If Len(txt5.Text) < 1 Then
        Select Case KeyAscii
        Case Asc("1"), Asc("2"), Asc("4")
            'Chang lam gi ca/Do nothing
        Case Else
            'Cac ky tu khac thi khong cho phep nhap/Do not allow for entering other
            KeyAscii = 0
        End Select
    Else
        KeyAscii = 0
    End If
End Sub

Private Sub txtFilter_Change()
    Dim iPos1 As Long, iPos2 As Long, iPos3 As Long
    Dim sCompare1 As String, sCompare2 As String
    Dim sCompare3 As String, sCompare4 As String
    Dim sFilter As String
    Dim lRecordCount As Long, sngQty As Single

    On Error GoTo txtFilter_Change_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If txtFilter.Text <> "" Then
        'Define the filter criteria
        iPos1 = InStr(1, txtFilter.Text, "*")
        If iPos1 > 0 Then
            iPos2 = InStr(iPos1 + 1, txtFilter.Text, "*")
        End If
        If iPos1 > 0 And iPos2 > 0 Then
            iPos3 = InStr(iPos2 + 1, txtFilter.Text, "*")
        End If
        'Suppose limit of the search criteria is 4
        If iPos1 > 0 And iPos2 > 0 And iPos3 > 0 Then
            sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
            sCompare2 = Mid(txtFilter.Text, iPos1 + 1, iPos2 - iPos1 - 1): sCompare2 = Trim(sCompare2)
            sCompare3 = Mid(txtFilter.Text, iPos2 + 1, iPos3 - iPos2 - 1): sCompare3 = Trim(sCompare3)
            If Len(txtFilter.Text) > iPos3 Then
                sCompare4 = Mid(txtFilter.Text, iPos3 + 1, Len(txtFilter.Text) - iPos3): sCompare2 = Trim(sCompare2)
                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# AND " & _
                          "Descriptions Like #*" & sCompare3 & "*# AND " & _
                          "Descriptions Like #*" & sCompare4 & "*# "

            Else
                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# AND " & _
                          "Descriptions Like #*" & sCompare3 & "*# "

            End If

        ElseIf iPos1 > 0 And iPos2 > 0 And iPos3 = 0 Then
            sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
            sCompare2 = Mid(txtFilter.Text, iPos1 + 1, iPos2 - iPos1 - 1): sCompare2 = Trim(sCompare2)
            If Len(txtFilter.Text) > iPos2 Then
                sCompare3 = Mid(txtFilter.Text, iPos2 + 1, Len(txtFilter.Text) - iPos2): sCompare3 = Trim(sCompare3)
                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# AND " & _
                          "Descriptions Like #*" & sCompare3 & "*# "

            Else
                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# "

            End If
        ElseIf iPos1 > 0 And iPos2 = 0 And iPos3 = 0 Then
            sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
            If Len(txtFilter.Text) > iPos1 Then
                sCompare2 = Mid(txtFilter.Text, iPos1 + 1, Len(txtFilter.Text) - iPos1): sCompare2 = Trim(sCompare2)

                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# "

            Else
                sFilter = "Descriptions Like #*" & sCompare1 & "*# "

            End If


        ElseIf iPos1 = 0 And iPos2 = 0 And iPos3 = 0 And txtFilter.Text <> "" Then
            sCompare1 = Mid(txtFilter.Text, 1, Len(txtFilter.Text)): sCompare1 = Trim(sCompare1)
            sFilter = "Descriptions Like #*" & sCompare1 & "*# "

        End If
        rsData.Filter = sFilter
        rsData.Sort = "Descriptions"
        lRecordCount = rsData.RecordCount
        If lRecordCount > 0 Then
            'Update the value
            Do Until rsData.EOF
                sngQty = sngQty + Val(rsData.Fields("CountQty").Value)
                rsData.MoveNext
            Loop
            txtTotalCountQty.Text = Format(sngQty, "##,##0.00")
        Else
            txtTotalCountQty.Text = Format(0, "##,##0.00")
        End If
    Else
        rsData.Filter = adFilterNone
        lRecordCount = rsData.RecordCount
        If lRecordCount > 0 Then
            'Update the value
            Do Until rsData.EOF
                sngQty = sngQty + Val(rsData.Fields("CountQty").Value)
                rsData.MoveNext
            Loop
            txtTotalCountQty.Text = Format(sngQty, "##,##0.00")
        Else
            txtTotalCountQty.Text = Format(0, "##,##0.00")
        End If

    End If

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

txtFilter_Change_Error:
    If bCentralErrorHandler("frmEditData", "txtFilter_Change", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub
Lê Thanh Nhân
 
Mã:
Private Sub txtMaterialNo_Change()
     Dim sFilter As String
     Dim lRecordCount As Long, sngQty As Single
     sFilter = txtMaterialNo.Text
     sFilter = "ItemCode LIKE '" & sFilter & "*'" & " AND Location='" & cbbSL.Text & "' "
     If Len(txtMaterialNo.Text) > 0 Then
         rsData.Filter = sFilter
         lRecordCount = rsData.RecordCount
         If lRecordCount > 0 Then
             'Update the value
             Do Until rsData.EOF
                 sngQty = sngQty + Val(rsData.Fields("CountQty").Value)
                 rsData.MoveNext
             Loop
             txtTotalCountQty.Text = Format(sngQty, "##,##0.00")
         Else
             txtTotalCountQty.Text = Format(0, "##,##0.00")
         End If
     Else
         rsData.Filter = adFilterNone
         lRecordCount = rsData.RecordCount
         If lRecordCount > 0 Then
             'Update the value
             Do Until rsData.EOF
                 sngQty = sngQty + Val(rsData.Fields("CountQty").Value)
                 rsData.MoveNext
             Loop
             txtTotalCountQty.Text = Format(sngQty, "##,##0.00")
         Else
             txtTotalCountQty.Text = Format(0, "##,##0.00")
         End If
     End If
 End Sub
 
 Private Sub txtMaterialNo_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
 
 'Set the length of the Material Number
     If Len(txtMaterialNo.Text) < 10 Then
         Select Case KeyAscii
         Case Asc("0") To Asc("9")
             'Chang lam gi ca/Do nothing
         Case Else
             'Cac ky tu khac thi khong cho phep nhap/Do not allow for entering other
             KeyAscii = 0
         End Select
     Else
         KeyAscii = 0
     End If
 End Sub
 
 Private Sub txtStockCardNo_Change()
     Dim sFilter As String
     Dim lRecordCount As Long, sngQty As Single
     sFilter = txtStockCardNo.Text
     sFilter = "StockCardNo LIKE '" & sFilter & "'" & " AND Location='" & cbbSL.Text & "' "
     If Len(txtStockCardNo.Text) > 0 Then
         rsData.Filter = sFilter
         lRecordCount = rsData.RecordCount
         If lRecordCount > 0 Then
             'Update the value
             Do Until rsData.EOF
                 sngQty = sngQty + Val(rsData.Fields("CountQty").Value)
                 rsData.MoveNext
             Loop
             txtTotalCountQty.Text = Format(sngQty, "##,##0.00")
         Else
             txtTotalCountQty.Text = Format(0, "##,##0.00")
         End If
     Else
         rsData.Filter = adFilterNone
         lRecordCount = rsData.RecordCount
         If lRecordCount > 0 Then
             'Update the value
             Do Until rsData.EOF
                 sngQty = sngQty + Val(rsData.Fields("CountQty").Value)
                 rsData.MoveNext
             Loop
             txtTotalCountQty.Text = Format(sngQty, "##,##0.00")
         Else
             txtTotalCountQty.Text = Format(0, "##,##0.00")
         End If
     End If
 End Sub
 
 Private Sub txtStockCardNo_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
     Select Case KeyAscii
     Case Asc("0") To Asc("9")
         'Chang lam gi ca/Do nothing
     Case Else
         'Cac ky tu khac thi khong cho phep nhap/Do not allow for entering other
         KeyAscii = 0
     End Select
 End Sub
 
 Private Sub UserForm_Initialize()
     Dim sSQL As String, i As Long, lLen As Long
     Dim sngQty As Single, lRecordCount As Long
     On Error GoTo UserForm_Initialize_Error
     With Application
         .Calculation = xlCalculationManual
         .ScreenUpdating = False
     End With
     Me.Height = 331
     If gcnAccess.state = ObjectStateEnum.adStateClosed Then
         Call ConnectToDatabase
     End If
     If bConnected = False Then
         MsgBox "Khong the ket noi voi Co so du lieu!" & vbCrLf & _
                "Xin ban kiem tra lai!" & vbCrLf & _
                "Can not connect to DB. Pls, check!", vbOKOnly, "Inf"
         End
     End If
     If gcnAccess.state = ObjectStateEnum.adStateClosed Then
         gcnAccess.Open
     End If
     sSQL = "SELECT ItemCode, UoM, Descriptions, CountQty, StockCardNo, Location, " & _
            "GoodsCondition, Remarks, DataEntryBy, AutoNumber " & _
            "FROM tbMain " & _
            "ORDER BY ItemCode ; "
 
     'When we set this property to adUseClient
     'So you can Sort at the Client
     With rsData
         .CursorLocation = adUseClient
         .Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
         lRecordCount = .RecordCount
     End With
     If lRecordCount > 0 Then
         With Me.MSHFlexGridDB
             Set .DataSource = rsData
             'Let the user resize the size of the Grid
             .AllowUserResizing = 3
             'Adjust the column width
             .ColWidth(0) = 0
             .ColWidth(1) = 1200
             .ColWidth(2) = 500
             .ColWidth(3) = 4200
 
             'Set the Grid
             .Gridlines = flexGridDots
             .BackColor = &H80FFFF
         End With
 
     End If
     With Me.cbbSL
         .AddItem "R001"
         .AddItem "P001"
         .AddItem "W001"
         .ListIndex = 0
     End With
 
 ErrorExit:
     With Application
         .Calculation = xlCalculationManual
         .ScreenUpdating = True
 
     End With
     Exit Sub
 
 UserForm_Initialize_Error:
     If bCentralErrorHandler("frmEditData", "UserForm_Initialize", , False) Then
         Stop
         Resume
     Else
         Resume ErrorExit
     End If
 End Sub
 
 Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
 'Do not allow the user Close the form by this way
     If CloseMode = vbFormControlMenu Then
         Cancel = True
     End If
 End Sub
Ở đây chúng ta có dùng kỹ thuật "Lọc"
Các bạn chú ý txtFilter ở sự kiện Change, người dùng có thể lọc dữ liệu rất nhanh.
Ví dụ: tôi muốn tìm mô tả sản phẩm là SPRING VENUS, 200X160X20CM
Tôi có thể nhập vào textbox này như sau: *VENUS*200*160*
Lập tức các bạn có ngay kết quả lọc.
Còn một số mẹo vặt khác, các bạn tham khảo nha.

Hy vọng với bài này, các bạn có một cái nhìn "thân thiện" với cái thằng "Excel".

Chúc các bạn thành công.

Lê Thanh Nhân

Mọi góp ý xin gởi về: ledothanhnhan@gmail.com
 
Xây dựng một ứng dụng xem tồn kho

Chào các bạn,
Một số bạn được làm việc trong một công ty có sử dụng các phần mềm nỗi tiếng thì thật là tuyệt. Nhưng lắm lúc các bạn cũng chán vì việc "bảo mật" của nó có đúng không?
Chẳng hạn chương trình "SAP" nổi tiếng
http://www.sap.com/solutions/business-suite/erp/index.epx
Công ty của bạn phải làm ba ca, nhưng ca ba người trửơng ca lại không được cấp quyền truy cập (vì tiền trả cho một user nhiều quá!). Vậy thì làm thế nào giúp trưởng ca có thể giải quyết được yêu cầu này mà không phải tốn thêm tiền. :(
Tôi có một giải pháp tạm thời như sau:
_Cần một user ID với phân quyền có thể trích xuất số liệu ra Excel.
_Viết một module đưa dữ liệu vào database.
_Viết một module để thể hiện bảng tồn kho cho người dùng xem xét.
Data.jpg


Bạn phải xem xét định dạng dữ liệu xúat ra của bạn. Định dạng này phải cố định. Ví dụ như hình sau:

Sau đó bạn thíet kế một form nhằm giúp người dùng import dữ liệu.
Chương trình sẽ kiểm tra: Header của khối dữ liệu cố định, kiểm tra dữ liệu trước khi nhập vào database.
Import.jpg


Đọan code đầy đủ của form trên như sau:
Mã:
Option Explicit
Private Sub cboChoseWB_Change()
Dim iSoLuongWS As Integer
Dim i As Integer
Dim sTenWB As String
    On Error Resume Next
    cboChoseWS.Clear
    sTenWB = cboChoseWB.Text
    iSoLuongWS = Workbooks(sTenWB).Worksheets.Count
    For i = 1 To iSoLuongWS
        cboChoseWS.AddItem Workbooks(sTenWB).Worksheets(i).Name
    Next i
    cboChoseWS.ListIndex = 0
End Sub

Private Sub cboChoseWS_Change()
    If cboChoseWS <> -1 Then
        sTenWorkbookThaoTac = cboChoseWB.Text
        sTenWorksheetThaoTac = cboChoseWS.Text
    End If
End Sub


Private Sub cmdExit_Click()

    On Error GoTo cmdExit_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    If gcnAccess.State = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If
    End
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Sub

cmdExit_Click_Error:
    If bCentralErrorHandler("frmInputData", "cmdExit_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If


End Sub

Private Sub cmdImportData_Click()

'Delete the data before import
    On Error GoTo cmdImportData_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    sTenWorkbookThaoTac = Me.cboChoseWB.Text
    sTenWorksheetThaoTac = Me.cboChoseWS.Text
    sTableInDBName = "tbStock"
    If ckbHearderChecking Then
        If Not CheckReqWorkbookName Then
            MsgBox "Khong dung nhu format yeu cau!" & vbCrLf & _
                   "This is not the requirement's format!" & vbCrLf & _
                   "Pls, check.", vbOKOnly, "Inf"
            Exit Sub
        End If
    End If
    If ckbDeleteBeforeImport Then
        Call DeleteAll("tbStock")
    End If
    'If the format correct then action
    If gcnAccess.State = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    If bConnected Then
        Call ADOFromExcelToAccess
    End If
    MsgBox "You have finished importing the data!", vbOKOnly, "Notice"

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Sub

cmdImportData_Click_Error:
    If bCentralErrorHandler("frmInputData", "cmdImportData_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If



End Sub

Private Sub UserForm_Initialize()
Dim iSoLuongWB As Integer
Dim i As Integer
    On Error Resume Next
    iSoLuongWB = Application.Workbooks.Count
    For i = 1 To iSoLuongWB
        cboChoseWB.AddItem Application.Workbooks(i).Name
    Next i
    cboChoseWB.ListIndex = 0
    ckbHearderChecking.Value = 1
    
End Sub
Function CheckReqWorkbookName() As Boolean
'This function will check the data format in the
'sTenWorkbookThaoTac, tai sheet sTenWorksheetThaoTac
'Neu dung thi tra ve True
Dim arrMangColHeader As Variant
Dim i As Long, sHeader As String
    On Error GoTo CheckReqWorkbookName_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    
    arrMangColHeader = Array("Material", "Material Description", "Plnt", "SLoc", _
                             "SL", "BUn", "Unrestr.", "Trans./Tfr", "Qual.Insp.", _
                             "Restricted", "Blocked", "Returns")
    'Check the sTenWorkbookThaoTac and sTenWorksheetThaoTac
    If Trim(sTenWorkbookThaoTac) = "" Or Trim(sTenWorksheetThaoTac) = "" Then
        CheckReqWorkbookName = False
        Exit Function
    End If

    'Check the format of the data in the ActiveWorkbook
    For i = 0 To UBound(arrMangColHeader)
        sHeader = Trim(Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B2").Offset(0, i))
        If sHeader <> arrMangColHeader(i) Then
            CheckReqWorkbookName = False
            Exit Function
        End If
    Next i
    CheckReqWorkbookName = True

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Function

CheckReqWorkbookName_Error:
    If bCentralErrorHandler("frmInputData", "CheckReqWorkbookName", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If


End Function

Sub ADOFromExcelToAccess()
' exports data from the active worksheet to a table in an Access database
' this procedure must be edited before use
Dim rs As ADODB.Recordset, r As Long
Dim dteDate As Date
Dim ItemCode As String
    On Error GoTo ADOFromExcelToAccess_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    If gcnAccess.State = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
        If gcnAccess.State = ObjectStateEnum.adStateClosed Then
            gcnAccess.Open
        End If
        Set rs = New ADODB.Recordset
        rs.Open sTableInDBName, gcnAccess, adOpenKeyset, adLockOptimistic, adCmdTable
        ' all records in a table
        r = 4    ' the start row in the worksheet
        Do While Len(Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B" & r).Formula) > 0
            ' repeat until first empty cell in column B
            With rs
                .AddNew    ' create a new record
                ' add values to each field in the record
                .Fields("MaterialNumber") = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B" & r).Value
                .Fields("Descriptions") = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("C" & r).Value
                .Fields("Plnt") = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("D" & r).Value
                .Fields("SLoc").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("E" & r).Value
                .Fields("UoM").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("G" & r).Value
                .Fields("UnrestQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("H" & r).Value
                .Fields("TransQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("I" & r).Value
                .Fields("QIQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("J" & r).Value
                .Fields("RestrictedQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("K" & r).Value
                .Fields("BlockedQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("L" & r).Value
                .Fields("ReturnsQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("M" & r).Value
                ' add more fields if necessary...
                .Update    ' stores the new record
            End With
            r = r + 1    ' next row
        Loop
        rs.Close
    End If
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Sub

ADOFromExcelToAccess_Error:
    If bCentralErrorHandler("frmInputData", "ADOFromExcelToAccess", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    MsgBox "You can not close by this way!Pls,Click Exit button!", vbOKOnly, "Notice"
    Cancel = True
End Sub

Lê Thanh Nhân
 
Lần chỉnh sửa cuối:
Sau khi dữ liệu đã được nhập thành công, bạn dùng kết nối để xem dữ liệu. Nó tương tự như trong lọat bài đầu tiên.
Code như sau:
Mã:
Option Explicit



Private Sub cbbSL_Change()

    rsData.Filter = "SLoc='" & cbbSL.Text & "'"
    rsData.Sort = "Descriptions"
End Sub

Private Sub cmdExit_Click()
    'Close the Connection before Close the Form
On Error GoTo cmdExit_Click_Error
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = False
      .DisplayAlerts = False
   End With
    Set rsData = Nothing
    If gcnAccess.State = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If
    End
ErrorExit:
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = True
      .DisplayAlerts = True
   End With
   Exit Sub

cmdExit_Click_Error:
If bCentralErrorHandler("frmQuery", "cmdExit_Click", , False) Then
     Stop
     Resume
Else
     Resume ErrorExit
End If
End Sub

Private Sub cmdExport_Click()
Dim lRecordCount As Long, i As Long, j As Integer
Dim rngRange As Range
    On Error GoTo cmdExport_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    i = 2
    lRecordCount = rsData.RecordCount
    If lRecordCount > 0 Then
        Set rngRange = Range("tbStock_Fig_Del")
        rngRange.ClearContents
        For j = 0 To rsData.Fields.Count - 1
            With Application.ThisWorkbook.Worksheets("STOCK_FIG").Range("A1")
                .Offset(0, j) = rsData.Fields(j).Name
            End With
        Next j
        rsData.MoveFirst
        Do Until rsData.EOF
            With Application.ThisWorkbook.Worksheets("STOCK_FIG").Range("A1")
                .Offset(i, 0) = rsData.Fields("MaterialNumber").Value
                .Offset(i, 1) = rsData.Fields("Descriptions").Value
                .Offset(i, 2) = rsData.Fields("Plnt").Value
                .Offset(i, 3) = rsData.Fields("SLoc").Value
                .Offset(i, 4) = rsData.Fields("UoM").Value
                .Offset(i, 5) = rsData.Fields("UnrestQty").Value
                .Offset(i, 6) = rsData.Fields("TransQty").Value
                .Offset(i, 7) = rsData.Fields("QIQty").Value
                .Offset(i, 8) = rsData.Fields("RestrictedQty").Value
                .Offset(i, 9) = rsData.Fields("BlockedQty").Value
                .Offset(i, 10) = rsData.Fields("ReturnsQty").Value
            End With
            rsData.MoveNext
            i = i + 1
        Loop
        rngRange.Columns.AutoFit
        ' Application.ThisWorkbook.Worksheets("SUMMARY").Range("A1").Offset(1, 0).CopyFromRecordset rsData
        ' This Method can not do when the Filter was applied to recordset
    End If
    Set rngRange = Nothing

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Sub

cmdExport_Click_Error:
    If bCentralErrorHandler("frmQuery", "cmdExport_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If


End Sub

Private Sub txtFilter_Change()
Dim iPos1 As Long, iPos2 As Long, iPos3 As Long
Dim sCompare1 As String, sCompare2 As String
Dim sCompare3 As String, sCompare4 As String
Dim sFilter As String
Dim lRecordCount As Long, sngQty As Single
    On Error GoTo txtFilter_Change_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    If txtFilter.Text <> "" Then
        'Define the filter criteria
        iPos1 = InStr(1, txtFilter.Text, "*")
        If iPos1 > 0 Then
            iPos2 = InStr(iPos1 + 1, txtFilter.Text, "*")
        End If
        If iPos1 > 0 And iPos2 > 0 Then
            iPos3 = InStr(iPos2 + 1, txtFilter.Text, "*")
        End If
        'Suppose limit of the search criteria is 4
        If iPos1 > 0 And iPos2 > 0 And iPos3 > 0 Then
            sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
            sCompare2 = Mid(txtFilter.Text, iPos1 + 1, iPos2 - iPos1 - 1): sCompare2 = Trim(sCompare2)
            sCompare3 = Mid(txtFilter.Text, iPos2 + 1, iPos3 - iPos2 - 1): sCompare3 = Trim(sCompare3)
            If Len(txtFilter.Text) > iPos3 Then
                sCompare4 = Mid(txtFilter.Text, iPos3 + 1, Len(txtFilter.Text) - iPos3): sCompare2 = Trim(sCompare2)
                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# AND " & _
                          "Descriptions Like #*" & sCompare3 & "*# AND " & _
                          "Descriptions Like #*" & sCompare4 & "*# "
                          
            Else
                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# AND " & _
                          "Descriptions Like #*" & sCompare3 & "*# "
                          
            End If

        ElseIf iPos1 > 0 And iPos2 > 0 And iPos3 = 0 Then
            sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
            sCompare2 = Mid(txtFilter.Text, iPos1 + 1, iPos2 - iPos1 - 1): sCompare2 = Trim(sCompare2)
            If Len(txtFilter.Text) > iPos2 Then
                sCompare3 = Mid(txtFilter.Text, iPos2 + 1, Len(txtFilter.Text) - iPos2): sCompare3 = Trim(sCompare3)
                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# AND " & _
                          "Descriptions Like #*" & sCompare3 & "*# "
                          
            Else
                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# "
                          
            End If
        ElseIf iPos1 > 0 And iPos2 = 0 And iPos3 = 0 Then
            sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
            If Len(txtFilter.Text) > iPos1 Then
                sCompare2 = Mid(txtFilter.Text, iPos1 + 1, Len(txtFilter.Text) - iPos1): sCompare2 = Trim(sCompare2)

                sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                          "Descriptions Like #*" & sCompare2 & "*# "
                    
            Else
                sFilter = "Descriptions Like #*" & sCompare1 & "*# "
                          
            End If


        ElseIf iPos1 = 0 And iPos2 = 0 And iPos3 = 0 And txtFilter.Text <> "" Then
            sCompare1 = Mid(txtFilter.Text, 1, Len(txtFilter.Text)): sCompare1 = Trim(sCompare1)
            sFilter = "Descriptions Like #*" & sCompare1 & "*# "
                      
        End If
        'Release before filter
        sFilter = sFilter & _
        "AND SLoc='" & cbbSL.Text & "'"
        Debug.Print sFilter
        Debug.Print "Pos 1 " & iPos1 & ":" & sCompare1
        Debug.Print "Pos 2 " & iPos2 & ":" & sCompare2
        Debug.Print "Pos 3 " & iPos3 & ":" & sCompare3
        Debug.Print "String 4:" & sCompare4
        rsData.Filter = sFilter
        rsData.Sort = "Descriptions"
        lRecordCount = rsData.RecordCount
        Debug.Print sFilter & vbCrLf & lRecordCount
        If lRecordCount > 0 Then
            'Update the value
            Do Until rsData.EOF
                sngQty = sngQty + Val(rsData.Fields("UnrestQty").Value)
                rsData.MoveNext
            Loop
            txtTotal.Text = Format(sngQty, "##,##0.00")
        Else
            txtTotal.Text = Format(0, "##,##0.00")
        End If
    Else
        rsData.Filter = adFilterNone
    End If

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Sub

txtFilter_Change_Error:
    If bCentralErrorHandler("frmQuery", "txtFilter_Change", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If


End Sub
Lê Thanh Nhân
 
Mã:
Private Sub UserForm_Activate()
    rsData.Filter = "SLoc='" & cbbSL.Text & "'"
    rsData.Sort = "Descriptions"
End Sub

Private Sub UserForm_Initialize()
Dim rsDataCount As Long
Dim sSQL As String, i As Long, lLen As Long
Dim sngQty As Single
    On Error GoTo UserForm_Initialize_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    If gcnAccess.State = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    If bConnected = False Then
        MsgBox "Khong the ket noi voi Co so du lieu!" & vbCrLf & _
               "Xin ban kiem tra lai!", vbOKOnly, "Thong bao"
        End
    End If
    If gcnAccess.State = ObjectStateEnum.adStateClosed Then
        gcnAccess.Open
        With Me.cbbSL
            .AddItem "R001"
            .AddItem "P001"
            .ListIndex = 0
        End With
        sSQL = "SELECT * " & _
               "FROM tbStock " & _
               "ORDER BY Descriptions ; "


        'When we set this property to adUseClient
        'So you can Sort at the Client
        With rsData
            .CursorLocation = adUseClient
            .Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic

            rsDataCount = .RecordCount
        End With
        If rsDataCount > 0 Then
            With Me.MSHFlexGridDB
                Set .DataSource = rsData
                'Let the user resize the size of the Grid
                .AllowUserResizing = 3
                'Adjust the column width
                .ColWidth(0) = 0
                .ColWidth(1) = 1500    'MaterialNumber
                .ColWidth(2) = 5000    'Description
                .ColWidth(3) = 500    'Plnt
                .ColWidth(4) = 500    'SLoc
                .ColWidth(5) = 500    'UoM
                'Set the Grid
                .Gridlines = flexGridInset
                .BackColor = &H80FFFF
            End With
            'Update the value
            Do Until rsData.EOF
                sngQty = sngQty + Val(rsData.Fields("UnrestQty").Value)
                rsData.MoveNext
            Loop
            txtTotal.Text = Format(sngQty, "##,##0.00") & "/" & rsDataCount
        Else
            txtTotal.Text = Format(0, "##,##0.00")
        End If

    End If
    'Pls take note that,
    'If you close the connection here, then you can not refresh the
    'Recordset when you want to sort or do something.

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Sub

UserForm_Initialize_Error:
    If bCentralErrorHandler("frmQuery", "UserForm_Initialize", , True) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'The purpose that Do not allow the user close the application
'by Click the X button on the Form
'Muc dich khong cho nguoi dung dong Form theo cach nay
    If CloseMode = vbFormControlMenu Then
        Cancel = True
    End If
End Sub

Private Sub BoxSetting(rsData As Recordset)
Dim col
    Me.MSHFlexGridDB.ColWidth(0) = 1440 * 0.25
    With Me.MSHFlexGridDB
        For col = 0 To rsData.Fields.Count - 1
            .ColWidth(col) = (92 * Len(rsData(col)))
        Next
    End With
End Sub
Hy vọng rằng với kiến thức trên các bạn sẽ phát triển cho mình các công cụ nhằm giúp cho "cuộc sống mưu sinh" được phần nào "thanh thản" hơn.

Lê Thanh Nhân
 
Chú ý trong phần trên có sử dụng hàm kiểm tra tiêu đề
Mã:
Function CheckReqWorkbookName() As Boolean
'This function will check the data format in the
'sTenWorkbookThaoTac, tai sheet sTenWorksheetThaoTac
'Neu dung thi tra ve True
Dim arrMangColHeader As Variant
Dim i As Long, sHeader As String
    On Error GoTo CheckReqWorkbookName_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    
    arrMangColHeader = Array("Material", "Material Description", "Plnt", "SLoc", _
                             "SL", "BUn", "Unrestr.", "Trans./Tfr", "Qual.Insp.", _
                             "Restricted", "Blocked", "Returns")
    'Check the sTenWorkbookThaoTac and sTenWorksheetThaoTac
    If Trim(sTenWorkbookThaoTac) = "" Or Trim(sTenWorksheetThaoTac) = "" Then
        CheckReqWorkbookName = False
        Exit Function
    End If

    'Check the format of the data in the ActiveWorkbook
    For i = 0 To UBound(arrMangColHeader)
        sHeader = Trim(Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B2").Offset(0, i))
        If sHeader <> arrMangColHeader(i) Then
            CheckReqWorkbookName = False
            Exit Function
        End If
    Next i
    CheckReqWorkbookName = True

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Function

CheckReqWorkbookName_Error:
    If bCentralErrorHandler("frmInputData", "CheckReqWorkbookName", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If

End Function

Lê Thanh Nhân.

Chú ý: các đọan code trên có một số tôi chưa tối ưu hóa (code chỉ có tính chất tham khảo thôi)
 
Thể theo yêu cầu

Chào các bạn,
Thật sự đây là bài tóan không phải dễ đối với các bạn mới chập chững bước vào. Nhưng theo tôi nghĩ nếu các bạn từng bước theo hướng dẫn của tôi, các bạn sẽ làm được thôi.
Theo yêu cầu tôi gởi lên file để theo dõi số lượng tồn kho.

Thân,

Lê Thanh Nhân
 

File đính kèm

Từng bước xây dựng chương trình

Chào các bạn,
Chúng ta hãy bắt đầu từng bước xây dựng chương trình.
Để xây dựng chương trình các bạn cần có kiến thức về ADO và cách sử dụng control MSHFLXGD.OCX.
Hơi khó phải không các bạn? Về ADO các bạn có thể xem bài viết tại www.levanduyetexcel.netfirms.com
Còn về việc sử dụng MSHFLXGD.OCX tôi nghỉ trong chương trình chúng ta sẽ hiểu thôi !
Trong File Stock_Check.xls chúng ta có 2 worksheet:
_STOCK_CHECK
_STOCK_FIG
Trong worksheet STOCK_CHECK chúng ta chỉ để 2 nút lệnh: Kiểm tồn kho và Cập nhật.
Mục đích của nút cập nhật là để đưa dữ liệu từ bên ngòai (Excel) vào database (*.mdb). Khi các bạn click vào nút Cập nhật một form sẽ hiện ra giúp các bạn chọn workbook, và worksheet có chứa dữ liệu các bạn cần "đẩy" vào database.
Import-1.jpg

Chúng ta sẽ có hai checkbox:
ckbHearderChecking và ckbDeleteBeforeImport
ckbHearderChecking: nhằm kiểm tra xem tiêu đề của dữ liệu cần đưa vào Database có theo như định dạng yêu cầu không? Chú ý với mỗi ứng dụng bạn phải viết lại hàm:
Mã:
Function CheckReqWorkbookName() As Boolean
'This function will check the data format in the
'sTenWorkbookThaoTac, tai sheet sTenWorksheetThaoTac
'Neu dung thi tra ve True
Dim arrMangColHeader As Variant
Dim i As Long, sHeader As String
    On Error GoTo CheckReqWorkbookName_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
        .DisplayAlerts = False
    End With
    
    arrMangColHeader = Array("Material", "Material Description", "Plnt", "SLoc", _
                             "SL", "BUn", "Unrestr.", "Trans./Tfr", "Qual.Insp.", _
                             "Restricted", "Blocked", "Returns")
    'Check the sTenWorkbookThaoTac and sTenWorksheetThaoTac
    If Trim(sTenWorkbookThaoTac) = "" Or Trim(sTenWorksheetThaoTac) = "" Then
        CheckReqWorkbookName = False
        Exit Function
    End If

    'Check the format of the data in the ActiveWorkbook
    For i = 0 To UBound(arrMangColHeader)
        sHeader = Trim(Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B2").Offset(0, i))
        If sHeader <> arrMangColHeader(i) Then
            CheckReqWorkbookName = False
            Exit Function
        End If
    Next i
    CheckReqWorkbookName = True

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
        .DisplayAlerts = True
    End With
    Exit Function

CheckReqWorkbookName_Error:
    If bCentralErrorHandler("frmInputData", "CheckReqWorkbookName", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Function
arrMangColHeader : mảng chứa các tiêu đề của các cột.
Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B2").Offset(0,
i)
Các bạn chú ý rằng chương trình sẽ bắt đầu tại B2. Tùy theo dữ liệu của bạn mà các bạn sửa lại đọan code trong chương trình của mình.
Tại cột B, chương trình sẽ quét cho tới khi không có giá trị trong cột này.
Nếu các bạn đã đọc qua các đọan code ở trên, chắc chắn các bạn sẽ hỏi tại sao thường có đọan code sau:
Mã:
If bCentralErrorHandler("frmInputData", "CheckReqWorkbookName", , [B]False[/B]) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
Mục đích của đọan code này là không cho xuất hiện báo lỗi. Khi có lỗi xãy ra, chương trình sẽ ghi ra một file text cùng thư mục với file chứa đọan code này. Tôi đã giới thiệu hàm này trong phần đầu bài viết.
Như vậy khi các bạn cung cấp file cho người dùng, người dùng sẽ bớt chán nãn khi xuất hiện nhiều thông báo lỗi.
Các bạn chú ý, nếu trong quá trình test thì tôi sẽ đặt thông số

bCentralErrorHandler("frmInputData", "CheckReqWorkbookName", , True)

Như vậy ngòai việc ghi lỗi ra file, nó cũng sẽ hiện thông báo cho tôi biết.

Thực chất, sau khi các bạn đã test hết rồi thì các bạn mới đặt lại thông số là False và cung cấp cho khách hàng.

Lê Thanh Nhân
 
Chỉnh sửa lần cuối bởi điều hành viên:
Trong thủ tục sự kiện UserForm_Initialze()
Chúng ta thêm vào cboChoseWB tên các workbook hiện đang mở. Điều này có nghĩa là bạn chỉ có thể nhập dữ liệu vào từ các Workbook đang mở.
Mã:
  Private Sub UserForm_Initialize()
  Dim iSoLuongWB As Integer
  Dim i As Integer
      On Error Resume Next
      iSoLuongWB = Application.Workbooks.Count
      For i = 1 To iSoLuongWB
          cboChoseWB.AddItem Application.Workbooks(i).Name
      Next i
      cboChoseWB.ListIndex = 0
      ckbHearderChecking.Value = 1
  End Sub
Sau đó dựa vào sự kiện Change của cboChoseWB để thêm vào tên các Worksheet của Workbook được chọn ở trên.
Mã:
  Private Sub cboChoseWB_Change()
  Dim iSoLuongWS As Integer
  Dim i As Integer
  Dim sTenWB As String
      On Error Resume Next
      cboChoseWS.Clear
      sTenWB = cboChoseWB.Text
      iSoLuongWS = Workbooks(sTenWB).Worksheets.Count
      For i = 1 To iSoLuongWS
          cboChoseWS.AddItem Workbooks(sTenWB).Worksheets(i).Name
      Next i
      cboChoseWS.ListIndex = 0
  End Sub
Cuối cùng dựa vào sự kiện Change của cboChoseWS để xác định hai biến
sTenWorkbookThaoTac
sTenWorksheetThaoTac
Mã:
  Private Sub cboChoseWS_Change()
      If cboChoseWS <> -1 Then
          sTenWorkbookThaoTac = cboChoseWB.Text
          sTenWorksheetThaoTac = cboChoseWS.Text
      End If
  End Sub
Và bây giờ chúng ta đi vào phân tích thủ tục chính khi click vào nút cmdImportData
Mã:
  Private Sub cmdImportData_Click()
  'Delete the data before import
      On Error GoTo cmdImportData_Click_Error
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = False
          .DisplayAlerts = False
      End With
      sTenWorkbookThaoTac = Me.cboChoseWB.Text
      sTenWorksheetThaoTac = Me.cboChoseWS.Text
      sTableInDBName = "tbStock"
      If ckbHearderChecking Then
          If Not CheckReqWorkbookName Then
              MsgBox "Khong dung nhu format yeu cau!" & vbCrLf & _
                     "This is not the requirement's format!" & vbCrLf & _
                     "Pls, check.", vbOKOnly, "Inf"
              Exit Sub
          End If
      End If
      If ckbDeleteBeforeImport Then
          Call DeleteAll("tbStock")
      End If
      'If the format correct then action
      If gcnAccess.State = ObjectStateEnum.adStateClosed Then
          Call ConnectToDatabase
      End If
      If bConnected Then
          Call ADOFromExcelToAccess
      End If
      MsgBox "You have finished importing the data!", vbOKOnly, "Notice"
   
  ErrorExit:
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = True
          .DisplayAlerts = True
      End With
      Exit Sub
   
  cmdImportData_Click_Error:
      If bCentralErrorHandler("frmInputData", "cmdImportData_Click", , False) Then
          Stop
          Resume
      Else
          Resume ErrorExit
      End If
  End Sub
Dựa vào sự lựa chọn của người dùng chúng ta gán giá trị cho hai biến
Mã:
  sTenWorkbookThaoTac = Me.cboChoseWB.Text
  sTenWorksheetThaoTac = Me.cboChoseWS.Text
Thực sự ra chúng ta không cần thực hiện lệnh gán này vì ở thủ tục sự kiện cbochoseWS_Change.
Sau đó dựa vào sự lựa chọn của người dùng khi click vào checkbox ckbHeaderChecking mà chúng ta kiểm tra xem các Header của các cột dữ liệu chúng ta muốn nhập vào có đúng hay không? Nếu không đúng thì thông báo và thoát.
Sau đó dựa vào việc chọn lựa của người dùng với checkbox ckbDeleteBeforeImport mà quyết định xóa dữ liệu trước khi nhập vào. Thủ tục xóa dữ liệu của bảng tbStock như sau: DeleteAll("tbStock")
Mã:
  Sub DeleteAll(ByVal sTableName As String)
  Dim rsData As ADODB.Recordset
  Dim cmAccess As ADODB.Command
  Dim sSQL As String
  Dim lAffected As Long
      On Error GoTo DeleteAll_Error
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = False
      End With
      'If the format correct then action
      If gcnAccess.State = ObjectStateEnum.adStateClosed Then
          Call ConnectToDatabase
      End If
      Set rsData = New ADODB.Recordset
      If gcnAccess.State = ObjectStateEnum.adStateClosed Then
          'Chi thuc hien khi da ket noi voi co so du lieu
          gcnAccess.Open
          sSQL = " DELETE FROM " & sTableName & ";"
          'Create and execute the Command Object
          Set cmAccess = New ADODB.Command
          cmAccess.ActiveConnection = gcnAccess
          cmAccess.CommandText = sSQL
          cmAccess.CommandType = adCmdText
          cmAccess.Execute lAffected, , adExecuteNoRecords
      End If
  ErrorExit:
      Set rsData = Nothing
      If gcnAccess.State = ObjectStateEnum.adStateOpen Then
          gcnAccess.Close
          bConnected = False
      End If
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = True
      End With
      Exit Sub
   
  DeleteAll_Error:
      If bCentralErrorHandler("MainModule", "DeleteAll", , False) Then
          Stop
          Resume
      Else
          Resume ErrorExit
      End If
  End Sub
Các bạn để ý rằng trong tất cả các thủ tục có xử lý số liệu trong các bảng Access, chúng ta thường có thủ tục ConnectToDatabase.
Các bạn chú ý trong Module PubConst chúng ta có khai báo
Option Explicit
'Declare the Public variance
Public gcnAccess As New ADODB.Connection 'Global connection Access
Biến này kiểu ADODB.Connection, nhằm kết nối với cơ sở dữ liệu. Kiểu Connection các bạn có thể hiểu “nôm na” như sau: Kiểu connection giống như khi các bạn nhấc máy điện thoại lên gọi đến số máy khác. Khi kết nối không được các bạn không thể trò chuyện với người ở máy bên kia. Tương tự đối với biến kiểu Connection. Một khi kết nối được thì bạn hãy nói đến chuyện thao tác với cơ sở dữ liệu. Sau khi kết nối thành công thì đặt biến bConnected = True
Mã:
Public Sub ConnectToDatabase()
  Const msMODULE As String = "MainMod"            'Module name
  Const sSOURCE As String = "ConnectToDatabase"            ' Sub name
  Dim sConnect As String
  Dim lAttempt As Long
  Dim sSQL As String
      On Error GoTo ErrorHandler
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = False
          .DisplayAlerts = False
      End With
      'Check if the connection has creased then exit sub
      If gcnAccess.State = ObjectStateEnum.adStateOpen Then
          MsgBox "The connection has creased!", vbOKOnly, "Notice"
          GoTo ErrorExit
      End If
      'Create the SConnect Path
      sConnect = "DSN=WareHouseDB;UID=admin;PWD=;"
      With gcnAccess
          .Mode = adModeReadWrite
          .ConnectionTimeout = 100
          .CursorLocation = adUseClient
          .ConnectionString = sConnect
          .Open
      End With
      bConnected = True
      'Close the connection to enable connection pooling
      If gcnAccess.State = ObjectStateEnum.adStateOpen Then gcnAccess.Close
  ErrorExit:
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = True
          .DisplayAlerts = True
      End With
      Exit Sub
   
  ErrorHandler:
   
      'We will try to make the connection 3 times before bailing out
     ‘Cố gắng kết nối với cơ sở dữ liệu 3 lần trước khi kết thúc
      If lAttempt < 3 And gcnAccess.Errors.Count > 0 Then
          If gcnAccess.Errors(0).NativeError = 17 Then
              lAttempt = lAttempt + 1
              Resume
          End If
      End If
      If bCentralErrorHandler(msMODULE, sSOURCE, , False) Then
          Stop
          Resume
      Else
          Resume ErrorExit
      End If
  End Sub
Đây có thể nói là thủ tục cơ bản khi bạn kết nối đến các cơ sở dữ liệu khác. Khi muốn kết nối với cơ sở dữ liệu khác bạn chỉ cần thay biến
Mã:
sConnect = "DSN=WareHouseDB;UID=admin;PWD=;"
Bạn tham khảo bài viết về Connection trong Topic khác.
Sau khi kiểm tra kết nối, và kết nôi với cơ sở dữ liệu chúng ta đến thủ tục chính. Thủ tục này nhằm đưa dữ liệu vào bảng của Access.
sTableInDBName: tên bảng dữ liệu bạn cần để nhập dữ liệu vào.
Mã:
Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B" & r).Formula
Chúng ta dựa vào dữ liệu của cột B để quyết định nhập liệu tiếp hay không.
Mã:
.Fields("MaterialNumber") = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B" & r).Value
Câu lệnh trên có nghĩa là giá trị dữ liệu của trường MaterialNumber sẽ bằng giá trị tại ô Range("B" & r).Value
Tương tự cho các trường khác.


Le Thanh Nhan
 
Mã:
  Sub ADOFromExcelToAccess()
  ' exports data from the active worksheet to a table in an Access database
  ' this procedure must be edited before use
  Dim rs As ADODB.Recordset, r As Long
  Dim dteDate As Date
  Dim ItemCode As String
      On Error GoTo ADOFromExcelToAccess_Error
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = False
          .DisplayAlerts = False
      End With
      If gcnAccess.State = ObjectStateEnum.adStateClosed Then
          Call ConnectToDatabase
          If gcnAccess.State = ObjectStateEnum.adStateClosed Then
              gcnAccess.Open
          End If
          Set rs = New ADODB.Recordset
          rs.Open sTableInDBName, gcnAccess, adOpenKeyset, adLockOptimistic, adCmdTable
          ' all records in a table
          r = 4    ' the start row in the worksheet
          Do While Len(Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B" & r).Formula) > 0
              ' repeat until first empty cell in column B
              With rs
                  .AddNew    ' create a new record
                  ' add values to each field in the record
                  .Fields("MaterialNumber") = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("B" & r).Value
                  .Fields("Descriptions") = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("C" & r).Value
                  .Fields("Plnt") = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("D" & r).Value
                  .Fields("SLoc").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("E" & r).Value
                  .Fields("UoM").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("G" & r).Value
                  .Fields("UnrestQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("H" & r).Value
                  .Fields("TransQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("I" & r).Value
                  .Fields("QIQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("J" & r).Value
                  .Fields("RestrictedQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("K" & r).Value
                  .Fields("BlockedQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("L" & r).Value
                  .Fields("ReturnsQty").Value = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("M" & r).Value
                  ' add more fields if necessary...
                  .Update    ' stores the new record
              End With
              r = r + 1    ' next row
          Loop
          rs.Close
      End If
  ErrorExit:
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = True
          .DisplayAlerts = True
      End With
      Exit Sub
   
  ADOFromExcelToAccess_Error:
      If bCentralErrorHandler("frmInputData", "ADOFromExcelToAccess", , False) Then
          Stop
          Resume
      Else
          Resume ErrorExit
      End If
  End Sub
Như vậy chúng ta đã hoàn thành phần cơ bản ĐƯA DỮ LIỆU TỪ BÊN NGOÀI VÀO BẢNG TRONG ACCESS.

Bây giờ chúng ta qua phần THỂ HIỆN DỮ LIỆU TỪ CƠ SỞ DỮ LIỆU
Phần này tương đối “dễ nuốt” hơn phần trên. Các bạn nên đọc phần SQL cơ bản trong trang web www.levanduyetexcel.netfirms.com
Nguyên lý của form frmQuery như sau:
Khi mở form lên, chúng ta sẽ kiểm tra kết nối và kết nối với cơ sở dữ liệu. Nếu kết nối không được thì thoát. Nếu kết nối được chúng ta sẽ dùng câu lệnh SQL để lấy dữ liệu về thông qua recordset rsData. Recordset này được khai báo tại module PubConst, hoặc bạn cũng có thể khai báo trên cùng của đoạn mã trong frmQuery như sau:
Mã:
Dim rsData As New ADODB.Recordset
Câu lệnh quan trọng ở đây là
Mã:
rsData.CursorLocation = adUseClient
Với câu lệnh này, chúng ta dễ dàng lọc, sắp xếp dữ liệu tại client. Đây là kỹ thuật chính của form này.
Cuối cùng bạn gán thuộc tính DataSource của MSHFlexGridDB cho rsData.
Mã:
Me.MSHFlexGridDB.DataSource = rsData
Đoạn mã của sự kiện Initialize của frmQuery như sau:
Mã:
  Private Sub UserForm_Initialize()
  Dim rsDataCount As Long
  Dim sSQL As String, i As Long, lLen As Long
  Dim sngQty As Single
      On Error GoTo UserForm_Initialize_Error
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = False
          .DisplayAlerts = False
      End With
      If gcnAccess.State = ObjectStateEnum.adStateClosed Then
          Call ConnectToDatabase
      End If
      If bConnected = False Then
          MsgBox "Khong the ket noi voi Co so du lieu!" & vbCrLf & _
                 "Xin ban kiem tra lai!", vbOKOnly, "Thong bao"
          End
      End If
      If gcnAccess.State = ObjectStateEnum.adStateClosed Then
          gcnAccess.Open
          With Me.cbbSL
              .AddItem "R001"
              .AddItem "P001"
              .ListIndex = 0
          End With
  [COLOR=red]        sSQL = "SELECT * " & _[/COLOR]
  [COLOR=red]               "FROM tbStock " & _[/COLOR]
  [COLOR=red]               "ORDER BY Descriptions ; "[/COLOR]
          'When we set this property to adUseClient
          'So you can Sort at the Client
          With rsData
              .CursorLocation = adUseClient
              .Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
              rsDataCount = .RecordCount
          End With
          If rsDataCount > 0 Then
              With Me.MSHFlexGridDB
                  Set .DataSource = rsData
                  'Let the user resize the size of the Grid
                  .AllowUserResizing = 3
                  'Adjust the column width
                  .ColWidth(0) = 0
                  .ColWidth(1) = 1500    'MaterialNumber
                  .ColWidth(2) = 5000    'Description
                  .ColWidth(3) = 500    'Plnt
                  .ColWidth(4) = 500    'SLoc
                  .ColWidth(5) = 500    'UoM
                  'Set the Grid
                  .Gridlines = flexGridInset
                  .BackColor = &H80FFFF
              End With
              'Update the value
              Do Until rsData.EOF
                  sngQty = sngQty + Val(rsData.Fields("UnrestQty").Value)
                  rsData.MoveNext
              Loop
              txtTotal.Text = Format(sngQty, "##,##0.00") & "/" & rsDataCount
          Else
              txtTotal.Text = Format(0, "##,##0.00")
          End If
   
      End If
      'Pls take note that,
      'If you close the connection here, then you can not refresh the
      'Recordset when you want to sort or do something.
  ErrorExit:
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = True
          .DisplayAlerts = True
      End With
      Exit Sub
   
  UserForm_Initialize_Error:
      If bCentralErrorHandler("frmQuery", "UserForm_Initialize", , True) Then
          Stop
          Resume
      Else
          Resume ErrorExit
      End If
  End Sub
Le Thanh Nhan
 
Tôi nghĩ thủ thuật thứ hai các bạn cần quan tâm ở thủ tục Private Sub txtFilter_Change()
Mã:
Private Sub txtFilter_Change()
  Dim iPos1 As Long, iPos2 As Long, iPos3 As Long
  Dim sCompare1 As String, sCompare2 As String
  Dim sCompare3 As String, sCompare4 As String
  Dim sFilter As String
  Dim lRecordCount As Long, sngQty As Single
      On Error GoTo txtFilter_Change_Error
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = False
          .DisplayAlerts = False
      End With
      If txtFilter.Text <> "" Then
          'Define the filter criteria
          iPos1 = InStr(1, txtFilter.Text, "*")
          If iPos1 > 0 Then
              iPos2 = InStr(iPos1 + 1, txtFilter.Text, "*")
          End If
          If iPos1 > 0 And iPos2 > 0 Then
              iPos3 = InStr(iPos2 + 1, txtFilter.Text, "*")
          End If
          'Suppose limit of the search criteria is 4
          If iPos1 > 0 And iPos2 > 0 And iPos3 > 0 Then
              sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
              sCompare2 = Mid(txtFilter.Text, iPos1 + 1, iPos2 - iPos1 - 1): sCompare2 = Trim(sCompare2)
              sCompare3 = Mid(txtFilter.Text, iPos2 + 1, iPos3 - iPos2 - 1): sCompare3 = Trim(sCompare3)
              If Len(txtFilter.Text) > iPos3 Then
                  sCompare4 = Mid(txtFilter.Text, iPos3 + 1, Len(txtFilter.Text) - iPos3): sCompare2 = Trim(sCompare2)
                  sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                            "Descriptions Like #*" & sCompare2 & "*# AND " & _
                            "Descriptions Like #*" & sCompare3 & "*# AND " & _
                            "Descriptions Like #*" & sCompare4 & "*# "
                            
              Else
                  sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                            "Descriptions Like #*" & sCompare2 & "*# AND " & _
                            "Descriptions Like #*" & sCompare3 & "*# "
                            
              End If
   
          ElseIf iPos1 > 0 And iPos2 > 0 And iPos3 = 0 Then
              sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
              sCompare2 = Mid(txtFilter.Text, iPos1 + 1, iPos2 - iPos1 - 1): sCompare2 = Trim(sCompare2)
              If Len(txtFilter.Text) > iPos2 Then
                  sCompare3 = Mid(txtFilter.Text, iPos2 + 1, Len(txtFilter.Text) - iPos2): sCompare3 = Trim(sCompare3)
                  sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                            "Descriptions Like #*" & sCompare2 & "*# AND " & _
                            "Descriptions Like #*" & sCompare3 & "*# "
                            
              Else
                  sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                            "Descriptions Like #*" & sCompare2 & "*# "
                            
              End If
          ElseIf iPos1 > 0 And iPos2 = 0 And iPos3 = 0 Then
              sCompare1 = Mid(txtFilter.Text, 1, iPos1 - 1): sCompare1 = Trim(sCompare1)
              If Len(txtFilter.Text) > iPos1 Then
                  sCompare2 = Mid(txtFilter.Text, iPos1 + 1, Len(txtFilter.Text) - iPos1): sCompare2 = Trim(sCompare2)
   
                  sFilter = "Descriptions Like #*" & sCompare1 & "*# AND " & _
                            "Descriptions Like #*" & sCompare2 & "*# "
                      
              Else
                  sFilter = "Descriptions Like #*" & sCompare1 & "*# "
                            
              End If
   
          ElseIf iPos1 = 0 And iPos2 = 0 And iPos3 = 0 And txtFilter.Text <> "" Then
              sCompare1 = Mid(txtFilter.Text, 1, Len(txtFilter.Text)): sCompare1 = Trim(sCompare1)
              sFilter = "Descriptions Like #*" & sCompare1 & "*# "
                        
          End If
          'Release before filter
          sFilter = sFilter & _
          "AND SLoc='" & cbbSL.Text & "'"
          Debug.Print sFilter
          Debug.Print "Pos 1 " & iPos1 & ":" & sCompare1
          Debug.Print "Pos 2 " & iPos2 & ":" & sCompare2
          Debug.Print "Pos 3 " & iPos3 & ":" & sCompare3
          Debug.Print "String 4:" & sCompare4
          [COLOR=red]rsData.Filter = sFilter[/COLOR]
          rsData.Sort = "Descriptions"
          lRecordCount = rsData.RecordCount
          Debug.Print sFilter & vbCrLf & lRecordCount
          If lRecordCount > 0 Then
              'Update the value
              'Tính tổng
              Do Until rsData.EOF
                  sngQty = sngQty + Val(rsData.Fields("UnrestQty").Value)
                  rsData.MoveNext
              Loop
              txtTotal.Text = Format(sngQty, "##,##0.00")
          Else
              txtTotal.Text = Format(0, "##,##0.00")
          End If
      Else
          rsData.Filter = adFilterNone
      End If
   
  ErrorExit:
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = True
          .DisplayAlerts = True
      End With
      Exit Sub
  txtFilter_Change_Error:
      If bCentralErrorHandler("frmQuery", "txtFilter_Change", , False) Then
          Stop
          Resume
      Else
          Resume ErrorExit
      End If
  End Sub
Thủ thuật chính ở đây là dựa vào những gì người dùng nhập vào để thiết lập chuổi lọc sFilter. Ví dụ: người dùng gõ vào textbox này như sau: Matt*190*91*25*

Có nghĩa là người dùng cần tìm các chuỗi : Matt, 190, 91, 25 trong chuỗi cần tìm. Nếu các bạn để ý, chương trình xử lý rất nhanh. Và các bạn sẽ có kết quả tìm kiếm trong chốc lát.
Sau đó dựa vào kết quả lọc được các bạn có thể xuất ra ngoài.
Mã:
  Private Sub cmdExport_Click()
  Dim lRecordCount As Long, i As Long, j As Integer
  Dim rngRange As Range
      On Error GoTo cmdExport_Click_Error
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = False
          .DisplayAlerts = False
      End With
      i = 2
      lRecordCount = rsData.RecordCount
      If lRecordCount > 0 Then
          Set rngRange = Range("tbStock_Fig_Del")
          rngRange.ClearContents
          For j = 0 To rsData.Fields.Count - 1
              With Application.ThisWorkbook.Worksheets("STOCK_FIG").Range("A1")
                  .Offset(0, j) = rsData.Fields(j).Name
              End With
          Next j
          rsData.MoveFirst
          Do Until rsData.EOF
              With Application.ThisWorkbook.Worksheets("STOCK_FIG").Range("A1")
                  .Offset(i, 0) = rsData.Fields("MaterialNumber").Value
                  .Offset(i, 1) = rsData.Fields("Descriptions").Value
                  .Offset(i, 2) = rsData.Fields("Plnt").Value
                  .Offset(i, 3) = rsData.Fields("SLoc").Value
                  .Offset(i, 4) = rsData.Fields("UoM").Value
                  .Offset(i, 5) = rsData.Fields("UnrestQty").Value
                  .Offset(i, 6) = rsData.Fields("TransQty").Value
                  .Offset(i, 7) = rsData.Fields("QIQty").Value
                  .Offset(i, 8) = rsData.Fields("RestrictedQty").Value
                  .Offset(i, 9) = rsData.Fields("BlockedQty").Value
                  .Offset(i, 10) = rsData.Fields("ReturnsQty").Value
              End With
              rsData.MoveNext
              i = i + 1
          Loop
          rngRange.Columns.AutoFit
          ' Application.ThisWorkbook.Worksheets("SUMMARY").Range("A1").Offset(1, 0).CopyFromRecordset rsData
          ' This Method can not do when the Filter was applied to recordset
      End If
      Set rngRange = Nothing
   
  ErrorExit:
      With Application
          .Calculation = xlCalculationManual
          .ScreenUpdating = True
          .DisplayAlerts = True
      End With
      Exit Sub
   
  cmdExport_Click_Error:
      If bCentralErrorHandler("frmQuery", "cmdExport_Click", , False) Then
          Stop
          Resume
      Else
          Resume ErrorExit
      End If
  End Sub
Vâng, như vậy là một ví dụ tương đối đầy đủ về việc đưa dữ liệu từ Excel sang Access. Và từ đó trích rút dữ liệu ra trở lại Excel.
Các bạn có thể phát triển từ ví dụ này sang nhiều ứng dụng khác.

Chúc các bạn thành công.

Lê Thanh Nhân
 
Bảng trong Access tbStock, các trường có kiểu dữ liệu như sau:
tbStock.jpg


Lê Thanh Nhân
 
Tham khảo code của IBM

Tôi xin giới thiệu một đọan code của IBM để các bạn tham khảo.
Ở đây hầu như giới thiệu với các bạn các thao tác cơ bản với cơ sở dữ liệu
Các bạn hãy từ từ xem và nghiền ngẫm nha, nhớ đọc thêm các tài liệu về ADO.
(Thật sự ra tôi muốn giới thiệu với các bạn về ADO.NET nhưng nghĩ rồi thôi. Chắc hãy thao tác với cái ADO này cái đã :) )

Mã:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' (c) Copyright IBM Corp. 2003  All rights reserved.
'
' This sample program is owned by International Business Machines
' Corporation or one of its subsidiaries ("IBM") and is copyrighted
' and licensed, not sold.
'
' You may copy, modify, and distribute this sample program in any
' form without payment to IBM,  for any purpose including developing,
' using, marketing or distributing programs that include or are
' derivative works of the sample program.
'
' The sample program is provided to you on an "AS IS" basis, without
' warranty of any kind.  IBM HEREBY  EXPRESSLY DISCLAIMS ALL
' WARRANTIES EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO
' THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTIC-
' ULAR PURPOSE. Some jurisdictions do not allow for the exclusion or
' limitation of implied warranties, so the above limitations or
' exclusions may not apply to you.  IBM shall not be liable for any
' damages you suffer as a result of using, modifying or distributing
' the sample program or its derivatives.
'
' Each copy of any portion of this sample program or any derivative
' work,  must include a the above copyright notice and disclaimer of
' warranty.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Option Explicit


' Create and return ADO Connection Object.
Public Function GetConnection( _
        strDBName As String, _
        Optional strUserName As String = "", _
        Optional strPassword As String = "") As ADODB.Connection
    On Error GoTo GetConnection_ErrHandler

    Dim strConnectionString As String
    strConnectionString = "Provider=IBMDADB2; DSN=" & strDBName
    If strUserName <> "" And strPassword <> "" Then
        strConnectionString = strConnectionString & "; User ID=" & strUserName _
                 & "; Password=" & strPassword
    End If
    
    'Create new ADO connection object
    Dim adoConnection As New ADODB.Connection
    With adoConnection
        .CursorLocation = adUseClient
        .ConnectionString = strConnectionString
    End With

    'Return new ADO connection object
    Set GetConnection = adoConnection
    Set adoConnection = Nothing
    Exit Function

GetConnection_ErrHandler:
    MsgBox "Error Code: " & err.Number & vbNewLine & _
            "Description: " & err.Description & vbNewLine & _
            "Source: " & err.Source, _
            vbOKOnly + vbCritical
    err.Clear
    Set GetConnection = Nothing
End Function


Sub ShowAllErrors(ByRef adoConnection As ADODB.Connection)
    If adoConnection.Errors.Count > 0 Then
        MsgBox "Error code:  " & adoConnection.Errors(0).Number & vbNewLine & _
                "Description: " & adoConnection.Errors(0).Description & vbNewLine & _
                "Source:      " & adoConnection.Errors(0).Source, _
                vbOKOnly + vbCritical
        adoConnection.Errors.Clear
    Else
        MsgBox "Error code:  " & err.Number & vbNewLine & _
                "Description: " & err.Description & vbNewLine & _
                "Source:      " & err.Source, _
                vbOKOnly + vbCritical
        err.Clear
    End If
End Sub


Sub InsertUsingInsert(ByRef adoConnection As ADODB.Connection)
    'Create ADO objects
    Dim adoCommand As ADODB.Command

    On Error GoTo InsertUsingInsert_ErrHandler

    Set adoCommand = New ADODB.Command

    With adoCommand
        .CommandType = adCmdText
        .ActiveConnection = adoConnection
        .CommandText = "INSERT INTO BASICADO (tINT, tVARCHAR, tDATE, tTIME, tDECIMAL) VALUES(?,?,?,?,?)"
    End With

    'Add Parameters to the Command object
    Dim adoParm_tINT As Parameter
    Set adoParm_tINT = adoCommand.CreateParameter("tINT", _
                    adInteger, _
                    adParamInput)
    Call adoCommand.Parameters.Append(adoParm_tINT)
    
    Dim adoParm_tVARCHAR As Parameter
    Set adoParm_tVARCHAR = adoCommand.CreateParameter("tVARCHAR", _
                    adVarChar, _
                    adParamInput, _
                    256)
    Call adoCommand.Parameters.Append(adoParm_tVARCHAR)
    
    Dim adoParm_tDATE As Parameter
    Set adoParm_tDATE = adoCommand.CreateParameter("tDATE", _
                    adDBDate, _
                    adParamInput)
    Call adoCommand.Parameters.Append(adoParm_tDATE)
    
    Dim adoParm_tTIME As Parameter
    Set adoParm_tTIME = adoCommand.CreateParameter("tTIME", _
                    adDBTime, _
                    adParamInput)
    Call adoCommand.Parameters.Append(adoParm_tTIME)
    
    Dim adoParm_tDECIMAL As Parameter
    Set adoParm_tDECIMAL = adoCommand.CreateParameter("tDECIMAL", _
                    adDecimal, _
                    adParamInput)
    Call adoCommand.Parameters.Append(adoParm_tDECIMAL)
    
    'Set the values and execute the 1st insert
    adoParm_tINT.Value = 1
    adoParm_tVARCHAR.Value = "Hello world at 1!"
    adoParm_tDATE.Value = "21/02/1997"
    adoParm_tTIME.Value = "12:00:01"
    adoParm_tDECIMAL.Value = "1111.11"
    Call adoCommand.Execute
    
    'Set the values and execute the 2nd insert
    adoParm_tINT.Value = 2
    adoParm_tVARCHAR.Value = "Hello world at 2!"
    adoParm_tDATE.Value = "01/03/1999"
    adoParm_tTIME.Value = "12:00:02"
    adoParm_tDECIMAL.Value = "2222.22"
    Call adoCommand.Execute

    Set adoCommand = Nothing
    Exit Sub

InsertUsingInsert_ErrHandler:
    Call ShowAllErrors(adoConnection)
End Sub


Sub InsertUsingRecordset(ByRef adoConnection As ADODB.Connection)
    
    'Create ADO objects
    Dim adoCommand As ADODB.Command
    Dim adoRecordSet As ADODB.Recordset

    On Error GoTo InsertUsingRecordset_ErrHandler

    Set adoCommand = New ADODB.Command

    With adoCommand
        .CommandType = adCmdText
        .ActiveConnection = adoConnection
        .CommandText = "SELECT tINT, tVARCHAR, tDATE, tTIME, tDECIMAL FROM BASICADO WHERE tINT = 0"
    End With

    'Create record set
    Set adoRecordSet = New ADODB.Recordset
    adoRecordSet.Open adoCommand, , adOpenStatic, adLockOptimistic
    
    'Update values for row 1
    adoRecordSet.AddNew
    adoRecordSet!tINT = 1
    adoRecordSet!tVARCHAR = "Hello world at 1!"
    adoRecordSet!tDATE = "21/02/1997"
    adoRecordSet!tTIME = "12:00:01"
    adoRecordSet!tDECIMAL = "1111.11"
    
    'Update values for row 2
    adoRecordSet.AddNew
    adoRecordSet!tINT = 2
    adoRecordSet!tVARCHAR.Value = "Hello world at 2!"
    adoRecordSet!tDATE = "01/03/1999"
    adoRecordSet!tTIME = "12:00:02"
    adoRecordSet!tDECIMAL = "2222.22"
    
    'Execute all inserts
    Call adoRecordSet.Update
    
    Set adoRecordSet = Nothing
    Set adoCommand = Nothing
    Exit Sub

InsertUsingRecordset_ErrHandler:
    Call ShowAllErrors(adoConnection)
End Sub


Sub UpdateUsingUpdate(ByRef adoConnection As ADODB.Connection)
    
    'Create ADO objects
    Dim adoCommand As ADODB.Command

    On Error GoTo UpdateUsingUpdate_ErrHandler

    Set adoCommand = New ADODB.Command

    With adoCommand
        .CommandType = adCmdText
        .ActiveConnection = adoConnection
        .CommandText = "UPDATE BASICADO " & _
                       "SET tVARCHAR = 'Bye world!' " & _
                       "WHERE tINT = ?"
    End With

    'Add Parameters to the Command object
    Dim adoParm_tINT As Parameter
    Set adoParm_tINT = adoCommand.CreateParameter("tINT", _
                    adInteger, _
                    adParamInput)
    Call adoCommand.Parameters.Append(adoParm_tINT)
    
    'Update all records having tINT = 1
    adoParm_tINT.Value = 1
    Call adoCommand.Execute
    
    Set adoCommand = Nothing
    Exit Sub

UpdateUsingUpdate_ErrHandler:
    Call ShowAllErrors(adoConnection)
End Sub

Lê Thanh Nhân
 
Mã:
Sub UpdateUsingRecordset(ByRef adoConnection As ADODB.Connection)
    
    'Create ADO objects
    Dim adoCommand As ADODB.Command
    Dim adoRecordSet As ADODB.Recordset

    On Error GoTo UpdateUsingRecordset_ErrHandler

    Set adoCommand = New ADODB.Command

    With adoCommand
        .CommandType = adCmdText
        .ActiveConnection = adoConnection
        'Update requires primary key, so need to select tKEY
        .CommandText = "SELECT tKEY, tINT, tVARCHAR, tDATE, tTIME, tDECIMAL FROM BASICADO"
    End With

    'Create record set
    Set adoRecordSet = New ADODB.Recordset
    adoRecordSet.Open adoCommand, , adOpenStatic, adLockOptimistic
    
    'Update all records having tINT = 2 (requires primary key)
    Call adoRecordSet.Find("tINT = 2")
    While Not adoRecordSet.EOF()
        adoRecordSet!tVARCHAR = "Bye world!"
        adoRecordSet.MoveNext
        Call adoRecordSet.Find("tINT = 2")
    Wend
    
    'Workaround for EOF ADO bug!
    If adoRecordSet.RecordCount > 0 Then
        adoRecordSet.MoveFirst
    End If
    
    'Execute the update
    Call adoRecordSet.Update

    Set adoRecordSet = Nothing
    Set adoCommand = Nothing
    Exit Sub

UpdateUsingRecordset_ErrHandler:
    Call ShowAllErrors(adoConnection)
End Sub


Sub DeleteUsingDelete(ByRef adoConnection As ADODB.Connection)
    
    'Create ADO objects
    Dim adoCommand As ADODB.Command

    On Error GoTo DeleteUsingDelete_ErrHandler

    Set adoCommand = New ADODB.Command

    With adoCommand
        .CommandType = adCmdText
        .ActiveConnection = adoConnection
        .CommandText = "DELETE FROM BASICADO WHERE tINT = ?"
    End With

    'Add Parameters to the Command object
    Dim adoParm_tINT As Parameter
    Set adoParm_tINT = adoCommand.CreateParameter("tINT", _
                    adInteger, _
                    adParamInput)
    Call adoCommand.Parameters.Append(adoParm_tINT)
    
    'Delete all records having tINT = 1
    adoParm_tINT.Value = 1
    Call adoCommand.Execute
    
    Set adoCommand = Nothing
    Exit Sub

DeleteUsingDelete_ErrHandler:
    Call ShowAllErrors(adoConnection)
End Sub


Sub DeleteUsingRecordset(ByRef adoConnection As ADODB.Connection)
    
    'Create ADO objects
    Dim adoCommand As ADODB.Command
    Dim adoRecordSet As ADODB.Recordset

    On Error GoTo DeleteUsingRecordset_ErrHandler

    Set adoCommand = New ADODB.Command

    With adoCommand
        .CommandType = adCmdText
        .ActiveConnection = adoConnection
        'Delete requires primary key, so need to select tKEY
        .CommandText = "SELECT tKEY, tINT, tVARCHAR, tDATE, tTIME, tDECIMAL FROM BASICADO"
    End With

    'Create record set
    Set adoRecordSet = New ADODB.Recordset
    adoRecordSet.Open adoCommand, , adOpenStatic, adLockOptimistic
    
    'Delete all records having tINT = 2 (requires primary key)
    Call adoRecordSet.Find("tINT = 2")
    While Not adoRecordSet.EOF()
        adoRecordSet.Delete
        adoRecordSet.MoveNext
        Call adoRecordSet.Find("tINT = 2")
    Wend
    
    'Workaround for EOF ADO bug!
    If adoRecordSet.RecordCount > 0 Then
        adoRecordSet.MoveFirst
    End If
    
    'Execute the delete
    Call adoRecordSet.Update

    Set adoRecordSet = Nothing
    Set adoCommand = Nothing
    Exit Sub

DeleteUsingRecordset_ErrHandler:
    Call ShowAllErrors(adoConnection)
End Sub


Public Function GetDateValue(ByRef strDate As String) As String
    If strDate <> "" Then
        GetDateValue = Format(DateValue(strDate), "mm/dd/yyyy")
        'GetDateValue = FormatDateTime(DateValue(strDate), vbGeneralDate)
    Else
        GetDateValue = ""
    End If
End Function


Public Function GetTimeValue(ByRef strTime As String) As String
    If strTime <> "" Then
        GetTimeValue = Format(TimeValue(strTime), "hh:nn:ss")
        'GetTimeValue = FormatDateTime(TimeValue(strTime), vbLongTime)
    Else
        GetTimeValue = ""
    End If
End Function



Function QueryRecords(ByRef adoConnection As ADODB.Connection) As ADODB.Recordset
    'Create ADO objects
    Dim adoCommand As ADODB.Command
    Dim adoRecordSet As ADODB.Recordset

    On Error GoTo QueryRecords_ErrHandler

    Set adoCommand = New ADODB.Command

    With adoCommand
        .CommandType = adCmdText
        .ActiveConnection = adoConnection
        .CommandText = "SELECT tINT, tVARCHAR, tDATE, tTIME, tDECIMAL FROM BASICADO"
    End With

    'Create record set
    Set adoRecordSet = New ADODB.Recordset
    adoRecordSet.Open adoCommand, , adOpenStatic, adLockOptimistic
    
    Set QueryRecords = adoRecordSet
    
    Set adoRecordSet = Nothing
    Set adoCommand = Nothing
    Exit Function

QueryRecords_ErrHandler:
    Call ShowAllErrors(adoConnection)
    Set QueryRecords = Nothing
End Function

Lê Thanh Nhân
 
Khảo sát thủ tục ADOFromExcelToAccess()

Chào các bạn,
Hôm nay chúng ta sẽ bàn về việc thiết kế bảng dữ liệu trong MS Access, và việc xuất dữ liệu ra bảng này.
Các bạn có nhớ rằng trước đây chúng ta dùng thủ tục Sub ADOFromExcelToAccess()
Thủ tục này là thủ tục chính cho việc đưa dữ liệu từ Excel-> Access.
Có thể nói thủ tục này gồm các bước như sau:

_Kiểm tra kết nối CSDL(Cơ sở dữ liệu), kết nối với CSDL. Nếu không kết nối được thì thông báo thoát.
_Dùng Recordset để đưa dữ liệu vào Access.
Mã:
If gcnAccess.State = ObjectStateEnum.adStateClosed Then
          Call ConnectToDatabase
          If gcnAccess.State = ObjectStateEnum.adStateClosed Then
              gcnAccess.Open
          End If
          Set rs = New ADODB.Recordset
          rs.Open sTableInDBName, gcnAccess, adOpenKeyset, adLockOptimistic, adCmdTable
....
End If
Các bạn có thấy đoạn code ở trên có gì không ổn không?
Nếu kết nối đang mở
Mã:
gcnAccess.State=ObjectStateEnum.adStateOpen
Thì việc xuất dữ liệu ra Access sẽ không thực hiện được.
Vậy các bạn phải sửa lại như sau:
Mã:
If gcnAccess.State = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    If gcnAccess.State = ObjectStateEnum.adStateClosed Then
        gcnAccess.Open
    End If
    [COLOR="Blue"]Set rs = New ADODB.Recordset[/COLOR]
    [COLOR="Blue"]rs.Open sTableInDBName, gcnAccess, adOpenKeyset, adLockOptimistic, adCmdTable[/COLOR]
...


Lê Văn Duyệt
 
Sau khi Recordset được Open thì chúng ta bắt đầu đưa dữ liệu vào như sau:

Mã:
r = 2    ' Hàng bắt đầu để xuất dữ liệu
    Do While
'Kiểm tra có dữ liệu hay không tại cột A.
'Chú ý cột này phải là cột chứa dữ liệu liên tục
 Len(Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("A" & r).Formula) > 0
        ' Thực hiện liên tục cho tới khi ô không có dữ liệu tại cột A
        With rs
            .AddNew    ' Đưa vào dữ liệu mới
            ' Đưa các giá trị vào trường dữ liệu
            ' Ở đây chúng ta đưa vào trường [COLOR=Blue][B]Payer[/B][/COLOR]
            .Fields("Payer") = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("A" & r).Value
            
            ' Các bạn có thể thêm các trường khác
            .Update    ' Cập nhật Recordset. Chú ý, nếu không cập nhật thì coi như "công toi"
        End With
        r = r + 1    ' tăng hàng quét dữ liệu lên 1
    Loop
'Kết thúc vòng lập 
'Đặt biến bImportSucced = True, để xác nhận dữ liệu xuất là thành công
    bImportSucced = True
    rs.Close

Các bạn thấy đoạn code trên có gì không ổn không?
Có đấy, nếu việc xuất không thành công? Thì có trời mới biết dữ liệu nào không xuất được.
Vậy chúng ta có thể thêm vào một đoạn mã để khi xuất không được dữ liệu sẽ được đưa ra một worksheet: ERRORS

Mã:
r = 2    ' Hàng bắt đầu để xuất dữ liệu
    Do While
'Kiểm tra có dữ liệu hay không tại cột A.
'Chú ý cột này phải là cột chứa dữ liệu liên tục
 Len(Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("A" & r).Formula) > 0
On Error Resume Next
        ' Thực hiện liên tục cho tới khi ô không có dữ liệu tại cột A
        With rs
            .AddNew    ' Đưa vào dữ liệu mới
            ' Đưa các giá trị vào trường dữ liệu
            ' Ở đây chúng ta đưa vào trường [COLOR=Blue][B]Payer[/B][/COLOR]
            .Fields("Payer") =                      Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("A" & r).Value
            
            ' Các bạn có thể thêm các trường khác
            .Update    ' Cập nhật Recordset. Chú ý, nếu không cập nhật thì coi như "công toi"
            If Err.Number<>0 Then
            'Trong trường hợp có lỗi xãy ra 
            'Đưa dữ liệu ra một worksheet nhằm tham khảo
         With Application.Workbooks(sTenWorkbookThaoTac).Worksheets("ERRORS").Range("A1")
        .Offset(k, 1) = Application.Workbooks(sTenWorkbookThaoTac).Worksheets(sTenWorksheetThaoTac).Range("A" & r).Value
                 k = k + 1
                 'Đặt lại Err
                 Err.Number=0
                 End With
            End If
        End With
        r = r + 1    ' tăng hàng quét dữ liệu lên 1
    Loop
'Kết thúc vòng lập 
'Đặt biến bImportSucced = True, để xác nhận dữ liệu xuất là thành công
    bImportSucced = True
    rs.Close

Như vậy sau khi xuất dữ liệu chúng ta có thể biết được bao nhiêu dữ liệu chúng ta không thế xuất được.
_Đóng kết nối và giải phóng biến

Lê Văn Duyệt
 
Lần chỉnh sửa cuối:
Tạo bảng trong MS Access

Sau bước đầu tiên là "khảo sát" dữ liệu muốn xuất ra xem nó thuộc dạng nào?
Bạn bắt đầu mở MS Access
MSAFull.jpg


Tạo cơ sở dữ liệu mới.
MSABlankDB.jpg


Đặt tên cho cơ sở dữ liệu mới này.
MSADatTenDB.jpg


Tạo bảng dữ liệu mới.
MSATaoBangInView.jpg



Tạo trường dữ liệu, ở đây các bạn có thể chọn các trường dữ liệu sau:
MSATaoField.jpg


_Text.
_Memo.
_Number.
_Date/Time.
_Currency.
_Autonumber.
_Yes/No.
_OLE Object.
_Hyperlink.

Đối với các trường dữ liệu là mã sản phẩm nên dùng là Text (lấy chiều dài là 10 ký tự, tôi sẽ nói thêm sau)
Mỗi bảng dữ liệu nên có một trường Autonumber, để truy vấn sửa chữa, cập nhật sau này (áp dụng cho doanh nghiệp vừa và nhỏ thôi)
Theo tôi để tránh "phiền toái" đối với trường dữ lệu Date, tôi dùng kiểu Text, nhưng ta phải vết thêm một số hàm để xử lý.

Thiết lập các thuộc tính cho trường dữ liệu:
_Primary: cột sẽ làm làm khóa chính; Unique; Ignore Nulls...

Và cuối cùng và lưu lại.

Lê Văn Duyệt
 
Lần chỉnh sửa cuối:
Sql

Để xây dựng ứng dụng kết hợp chúng ta cần phải biết căn bản SQL.
Các bạn có thể tham khảo tại http://www.levanduyetexcel.netfirms.com/Sql_for_beginners.htm
Ở đây tôi xin giới thiệu một ít về SQL.
SQL là gì? SQL là một ngôn ngũ máy tính chuẩn để truy nhập và thao tác xử lý hầu hết mọi hệ CSDL.

SQL (Structured Quẻy Language)
  • SQL cho phép bạn truy nhập xử lý cơ sở dữ liệu.
  • SQL là một ngôn ngữ máy tính theo chuẩn ANSI
  • SQL có thể thực thi những truy vấn trên CSDL
  • SQL có thể dùng trích rút dữ liệu từ các bảng
  • SQL có thể chèn những mẫu tin hay dòng dữ liệu mới vào trong một bảng CSDL
  • SQL có thể xóa những dòng mẫu tin từ một bảng CSDL

Một bảng dữ liệu bao gồm
  • Trường dữ liệu (cột trong Excel)
  • Một một hàng dữ liệu bao gồm nhiều cột chúng ta gọi là một record (hàng trong Excel
Tập họp tất cả các hàng dữ liệu chúng ta gọi là bảng dữ liệu.
Vậy trong Excel khi ta thực hiện việc lọc dữ liệu bằng Auto Filter/Advanced Filter, điều này cũng giống như việc chúng ta lọc dữ liệu trong bảng dữ liệu của MS Access.

Đối với một dữ liệu lớn trong Excel (Ví dụ trên 10,000 dòng) thì việc lọc dữ liệu đã gặp khó khăn về tốc độ. Đó cũng là một trong lý do chúng ta Xây dựng ứng dụng kết hợp .

Lệnh SELECT
Mã:
SELECT [DISTINCT] cột dữ liệu
FROM tên bảng dữ liệu
WHERE điều kiện (*)
GROUP BY cột dữ liệu
ORDER BY cột dữ liệu [ASC] hoặc [DESC]


(*)Điều kiện chúng ta có thể dùng các toán tử sau:
Mã:
=           Bằng nhau
<>       Không bằng nhau
>          Lớn hơn
<          Nhỏ hơn
>=        Lớn hơn hoặc bằng
<=         Nhỏ hơn hoặc bằng
BETWEEN    Giữa một phạm vi giá trị
LIKE       Tìm kiếm so khớp theo mẫu
AND/OR
Chú ý: Trong một số phiên bản SQL, toán tử <> có thể được viết như sau: !=

Ví dụ tôi có một bảng dữ liệu tên tbSanLuongSX gồm các trường:
Mã sản phẩm (MSP), Mô tả (MoTa), Số lượng (SL), Nhập xuât (NX), Ngày sản xuất (NgaySX), Ca sản xuất (CaSX)

Bây giờ tôi muốn lọc sản phẩm từ ngày A đến ngày B thì chúng ta làm sao?
Chúng ta viết câu lệnh SQL sau:
Mã:
SELECT MSP, MoTa, SL, NgaySX
FROM tbSanLuongSX
WHERE NgaySX BETWEEN #A# AND #B#
ORDER BY NgaySX;

Giải thích: chọn Mã sản phẩm, Mô tả, Số lượng, Ngày sản xuất từ bảng tbSanLuongSX, nếu Ngày sản xuất trong khoảng A và B, sắp xếp theo NgaySX.

Bây giờ tôi muốn tính tổng sản phẩm sản xuất từ ngày A đến ngày B thì chúng ta làm sao?
Chúng ta viết câu lệnh SQL sau:
Mã:
SELECT MSP, MoTa, SUM(SL) AS [TONG]
FROM tbSanLuongSX
WHERE NgaySX BETWEEN #A# AND #B#
GROUP BY MSP, MoTa
ORDER BY NgaySX;

Chú ý: SUM(SL) AS [TONG] có nghĩa trường SUM(SL) có tên bí danh (alias) là TONG.
Chúng ta có thể sử dụng các hàm khác như Avg, Min, Max, Count

Giải thích: chọn Mã sản phẩm, Mô tả, Tổng(Số lượng) từ bảng tbSanLuongSX, nếu Ngày sản xuất trong khoảng A và B, sắp xếp theo NgaySX.

Giả sử các bạn muốn chọn ra chỉ các mã sản phẩm không trùng nhau, các bạn phải dùng câu lệnh như thế nào?
Mã:
SELECT DISTINCT MSP, MoTa
FROM tbSanLuongSX;

SELECT DISTINCT: có nghĩa là chỉ chọn unique record mà thôi.

Giả sử tôi có một bảng khác là tbMaSanPham gồm các trường: Mã sản phẩm (MSP), Mô tả sản phẩm (MoTa), Đơn vị tính (DVT)

Bây giờ tôi muốn tính tổng các sản phẩm sản xuất chỉ có trong bảng tbMaSanPham thì sao?
Chúng ta viết câu lệnh SQL sau:
Mã:
SELECT MSP, MoTa, SUM(SL) AS [TONG]
FROM tbSanLuongSX
WHERE MSP IN (SELECT DISTINCT MSP FROM tbMaSanPham)
GROUP BY MSP, MoTa
ORDER BY NgaySX;

Các bạn thấy câu lệnh SELECT DISTINCT MSP FROM tbMaSanPham
Trả về các giá trị unique của mã sản phẩm. Như vậy chúng ta có thể dùng các câu lệnh SELECT lồng nhau như trên.


Lê Văn Duyệt
 
Lần chỉnh sửa cuối:
Chèn dòng dữ liệu
Chúng ta dùng câu lệnh SQL sau:
Mã:
INSERT INTO tên bảng dữ liệu(cột dữ liệu 1, cột dữ liệu 2,...)
VALUES (giá trị 1, giá trị 2,...)

Giả sử tôi muốn chèn một dòng vào bảng tbMaSanPham thì chúng ta dùng câu lệnh SQL sau:

Mã:
INSERT INTO tbMaSanPham(MSP, MoTa, DVT)
VALUES('2200001245','Hàng đặc biệt', 'Cái');

Câp nhật dữ liệu
Chúng ta dùng câu lệnh SQL sau:
Mã:
UPDATE tên bảng dữ liệu
SET tên cột dữ liệu 1= giá trị mới
WHERE tên cột dữ liệu = giá trị điều kiện lọc

Bây giờ tôi muốn cập nhật mã 2200001245, với đơn vị tính là hộp
Chúng ta dùng câu lệnh SQL sau:
Mã:
UPDATE tbMaSanPham
SET DVT = 'hộp'
WHERE MSP = '2200001245'

Xóa dữ liệu
Chúng ta dùng câu lệnh SQL sau:
Mã:
DELETE tên bảng dữ liệu
WHERE tên cột dữ liệu = giá trị điều kiện lọc

Bây giờ tôi muốn xóa mã 2200001245
Chúng ta dùng câu lệnh SQL sau:
Mã:
DELETE tbMaSanPham
WHERE MSP = '2200001245'

Lê Văn Duyệt
 
Các hàm SQL
SQL cung cấp rất nhiều hàm nội tại cho phép đếm, tính tổng, thống kê

Cú pháp hàm:
Mã:
SELECT hàm(cột)
FROM tên bảng dữ liệu

Các loại hàm:
Các loại hàm trong SQL có thể phân loại theo nhóm như sau:
_Hàm tổng (Aggregate function): đây là các hàm tính tổng trên tập hợp và trả về một giá trị đơn duy nhất. Việc tính tổng có thể là phép đếm COUNT, phép cộng SUM, trung bình AVG
Chú ý: Nếu sử dụng giữa nhiều biểu thức khác trong danh sách của lệnh SELECT, lệnh SELECT phải có mệnh đề nhóm GROUP BY kèm theo (Giống ví dụ trên tôi đã đề cập).
_Hàm vô hướng (scalar functions): hàm vô hướng thường dùng tính toán trên một giá trị đơn. Ví dụ NOW(), LEN()

Mỗi hệ quản trị CSDL đề có những hàm cài đặt cụ thể và có thể khác nhau.

Danh sách các hàm tổng của SQL
Mã:
Hàm               Mô tả
AVG(cột)          Trả về trị trung bình của các giá trị cột
COUNT(cột)        Đếm số dòng (không tính giá trị NULL) của cột
COUNT(*)          Trả về số dòng được chọn từ SELECT
MAX(cột)          Trả về giá trị lớn nhất
MIN(cột)          Trả về giá trị nhỏ nhất
SUM(cột)          Trả về tổng các giá trị cột

Danh sách các hàm vô hướng trong MS Access và SQL Server
Mã:
Hàm                       Mô tả
UCASE(c)                  Chuyển chữ thường thành chữ hoa
LCASE(c)                  Chuyển chuổi thành chữ thường
MID(c,bắt đầu, kết thúc)  Copy chuổi con trong một chuổi
LEN(c)                    Trả về chiều dài chuổi
INSTR(c)                  Xác định vị trí ký tự trong chuổi
LEFT(c,số ký tự)          Cắt chuổi từ bên trái
RIGHT(c, số ký tự)        Cắt chuổi từ bên phải
ROUND(c, decimals)        Hàm làm tròn
MOD(x,y)                  Hàm chia lấy phần dư
NOW(x,y)                  Hàm trả về ngày tháng hiện hành
FORMAT(c, format)         Hàm định dạng
DATEDIFF(d, ngày1, ngày2) Hàm so sánh ngày tháng và thời gian


Lê Văn Duyệt
 
Như trên tôi đã giới thiệu về GROUP BY
Nay tôi xin giới thiệu về HAVING.
Do mệnh đề WHERE không thể sử dụng trong các hàm tổng, nên HAVING được thêm vào cấu trúc lệnh SQL. Nếu không có HAVING chúng ta không thể nào kiểm tra các kết quả trong từng nhóm.

Cú pháp HAVING như sau:
Mã:
SELECT cột dữ liệu 1, SUM(cột dữ liệu 2)
FROM tên bảng dữ liệu
GROUP BY cột dữ liệu
HAVING SUM(cột dữ liệu 2) Giá trị điều kiện


Giả sử tôi muốn kiểm tra xem sản lượng sản xuất của một nhóm sản phẩm nào đó lớn hơn 2000 của bảng sau:
Mã:
tbSanLuongSanXuat: Mã sản phẩm (MSP), Mô tả (MoTa), Số lượng (SL), Ngày sản xuất (NSX), Nhóm sản phẩm

Câu lệnh SQL sẽ như sau:
Mã:
SELECT MSP, MoTa, SUM(SL)
FROM tbSanLuongSanXuat
GROUP BY MSP, MoTa
HAVING SUM(SL)>2000

Lê Văn Duyệt
 
Lần chỉnh sửa cuối:
Trở về với ứng dụng xem tồn kho.

Thông thường sau khi xem tồn kho, chúng ta muốn in ra một danh sách các vật tư yêu cầu cho một ca làm việc. Nếu kết hợp được việc này thì sẽ tốt hơn.

Các bạn xem hình sau:

InBaoCao1.jpg


Nhắc lại, sau khi các bạn xem tồn kho, các bạn muốn in nhiều mục vật tư và đưa danh sách này cho nhân viên kho có trách nhiệm xuất vật tư.
Mẫu yêu cầu như sau:

InBaoCao2.jpg


Các bạn có để ý rằng thông thường định dạng của các báo cáo người viết thường dùng font có độ rộng bằng nhau ví dụ như font: Courier (hoặc new courier).
Vậy yêu cầu thêm của chúng ta là khi người dùng Double-Click vào MSHFlexGrid thì sẽ hỏi người dùng muốn đề nghị xuất bao nhiêu. Sau khi người dùng trả lời mục này sẽ được đưa vào một ListBox bên dưới.
Chúng ta cũng muốn rằng khi người dùng Double_Click vào một mục của Listbox thì mục này sẽ bị loại ra khỏi ListBox.

Đoạn code thêm vào sẽ như sau:

Mã:
Private Sub MSHFlexGridDB_DblClick()
 Dim faIndex As Variant, Ans As Variant
    Dim sMaterialNumber As String * 11, sMaterialDes As String * 41, sUoM As String * 5
    Dim sCombine As String
On Error GoTo MSHFlexGridDB_DblClick_Error
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = False
   End With
    faIndex = (Me.MSHFlexGridDB.RowSel) * Me.MSHFlexGridDB.Cols
    sMaterialNumber = Me.MSHFlexGridDB.TextArray(faIndex + 1)
    sMaterialDes = Me.MSHFlexGridDB.TextArray(faIndex + 2)
    sUoM = Me.MSHFlexGridDB.TextArray(faIndex + 5)
    Ans = Application.InputBox("Ban muon de nghi xuat vat tu " & vbNewLine & _
                               sMaterialNumber & "| " & sMaterialDes & vbNewLine & _
                               "Voi so luong: ", "So luong can", 10, , , , 1)
                               
    If IsNumeric(Ans) And Ans <> "False" Then
        sCombine = sMaterialNumber & "| " & sMaterialDes & "| " & sUoM & "| " & Ans
        Me.LstRmRequirement.AddItem sCombine
    End If
ErrorExit:
   With Application
      .Calculation = xlCalculationManual
      .ScreenUpdating = True
   End With
   Exit Sub
MSHFlexGridDB_DblClick_Error:
If bCentralErrorHandler("frmQuery", "MSHFlexGridDB_DblClick", , False) Then
     Stop
     Resume
Else
     Resume ErrorExit
End If
End Sub

Đối với đoạn code khi người dùng double click vào Listbox sẽ như sau:

Mã:
Private Sub LstRmRequirement_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
'Delete the item when
'user double click on to ListBox
    Dim lIndex As Long
    On Error GoTo LstRmRequirement_DblClick_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If Me.LstRmRequirement.ListIndex <> -1 Then
        lIndex = Me.LstRmRequirement.ListIndex
        Me.LstRmRequirement.RemoveItem lIndex
    End If
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

LstRmRequirement_DblClick_Error:
    If bCentralErrorHandler("frmQuery", "LstRmRequirement_DblClick", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If

End Sub

Chúng ta cũng tạo một nút lệnh để khi người dùng click vào đó nó sẽ hiện file text đã được định dạng theo mẫu báo cáo yêu cầu.
Mã:
Private Sub cmdPrint_Click()
    Dim vPrint As Variant
    On Error GoTo cmdPrint_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If Me.LstRmRequirement.ListCount > 0 Then
        With Me.LstRmRequirement
            .AddItem "**************************************************************"
            .AddItem " "
            .AddItem " "
            .AddItem " "
            .AddItem " "
            .AddItem "          YEU CAU BOI/REQUIRED BY"
        End With
        ListBoxUtils.SaveListBoxToFile Me.LstRmRequirement, "RawMaterialRequirement.txt", False
        MainMod.StartDoc GetLocalDirectory & "\" & "RawMaterialRequirement.txt"
    End If
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

cmdPrint_Click_Error:
    If bCentralErrorHandler("frmQuery", "cmdPrint_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Như vậy các bạn sẽ có phiếu yêu cầu vật tư theo ý muốn của bạn.
Chúc các bạn thành công.

Lê Văn Duyệt
 
Các bạn chú ý:
Nếu các bạn không cho người dùng xóa từ hàng số 1 đến số 4 thì các bạn phải chỉnh lại đoạn code sau:
Mã:
If Me.LstRmRequirement.ListIndex <> -1 And Me.LstRmRequirement.ListIndex>3 Then
        lIndex = Me.LstRmRequirement.ListIndex
        Me.LstRmRequirement.RemoveItem lIndex
End If

Chúc các bạn thành công.

Lê Văn Duyệt
 
Xây dựng form log-in

Thông thường đối với một phần mềm, ứng dụng người dùng đều phải log-in vào rồi mới được sử dụng.
Dựa vào việc log-in vào hệ thống sẽ:
_ Phân quyền.
_ Ghi chép lại các thao tác của người dùng.

Tôi xin giới thiệu với các bạn một ví dụ tương tự:
Đầu tiên chúng ta sẽ thiết kế một bảng đơn giản trong MS Access như sau:

tb_user.jpg


Các bạn có thể thiết kế thêm các trường khác tùy mục đích của mình. Tôi chỉ xin giới thiệu một ví dụ đơn giản nhất.

Sau đó mỗi khi người dùng mở file Ms Excel của bạn, bạn yêu cầu người dùng phải truy nhập vào trước. Sau khi người dùng truy nhập vào, chúng ta sẽ lưu lại trong một ô của một worksheet ẩn. Dựa vào tên người dùng chúng ta sẽ kiểm tra hoặc cho phép người dùng sử dụng một số chức năng của chương trình.

Mã:
Private Sub Workbook_Open()

    On Error GoTo Workbook_Open_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    Call ConnectToDatabase
    Call CreateMenuAll(False, True, True)
    frmLogIn.Show

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

Workbook_Open_Error:
    If bCentralErrorHandler("ThisWorkbook", "Workbook_Open", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Trong đoạn code trên mỗi khi workbook mở chúng ta:
_Kết nối vào CSDL
_Tạo Menu
_Hiện form yêu cầu người dùng nhập vào. Nếu người dùng không truy nhập vào thì không thể dùng file ứng dụng của chúng ta.
Form truy nhập

frmLogIn.jpg


Thông báo sau khi người dùng truy nhập vào

ThongbaoLogIn.jpg


Các bạn cũng có thể viết thủ tục Auto_Open() để thực hiện việc này.

Code của form như sau:
Mã:
Option Explicit
Dim iCount     As Long    'To count the error Log-In
Private Sub cmdLogIn_Click()
Dim sUserName As String, sUserSoSanh As String
Dim sPass As String, sPassSoSanh As String
Dim sRight     As String
Dim rngRange   As Range
    On Error GoTo cmdLogIn_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If iCount > 3 Then
        MsgBoxUni VNI("You have enter more than " & iCount & " times!" & vbCrLf & _
                      "Pls, contact your Administrator." & vbCrLf & _
                      "Baïn ñaõ truy caäp hôn 3 laàn" & vbCrLf & _
                      "Xin lieân heä vôùi Administrator."), vbOKOnly, VNI("Thoâng baùo")
        End
    End If
    sUserName = txtTenTruyCap.Text
    sUserSoSanh = UserExist(sUserName)
    'Check the user name
    If Len(Trim(sUserSoSanh)) = 0 Then
        MsgBoxUni VNI("This user does not exist!" & vbCrLf & _
                      "Teân naøy khoâng toàn taïi!"), vbOKOnly, VNI("Thoâng baùo")
        txtTenTruyCap.Text = ""
        txtPass.Text = ""
        txtTenTruyCap.SetFocus
        iCount = iCount + 1
    End If
    sPass = txtPass.Text
    'Get Password
    sPassSoSanh = GetUserPassword(sUserName)
    'Get right of User
    sRight = GetUserRight(sUserName)
    If sPass = sPassSoSanh And Len(Trim(sUserSoSanh)) > 0 Then
        If Len(Trim(sRight)) > 0 Then
            MsgBoxUni VNI("Chaøo möøng baïn ñeán vôùi    " & vbCrLf & _
                          "COÂNG CUÏ QL KHO              " & vbCrLf & _
                          "Taùc giaû: Leâ Vaên Duyeät" & vbCrLf & _
                          "Baïn ñaõ truy caäp vôùi quyeàn " & sRight), vbOKOnly, VNI("Thoâng baùo")
            Application.Range("UserName").Value = sUserSoSanh
        Else
            MsgBoxUni VNI("Chaøo möøng baïn ñeán vôùi    " & vbCrLf & _
                          "COÂNG CUÏ QL KHO              " & vbCrLf & _
                          "Taùc giaû: Leâ Vaên Duyeät" & vbCrLf & _
                          "Baïn ñaõ truy caäp vôùi quyeàn " & sRight), vbOKOnly, VNI("Thoâng baùo")
            iCount = iCount + 1
            Application.Range("UserName").Value = sUserSoSanh
        End If
    ElseIf Len(Trim(sUserSoSanh)) > 0 Then
        MsgBoxUni VNI("Wrong Password !" & vbCrLf & _
                      "Pls enter again." & vbCrLf & _
                      "Sai Password ! Xin kieåm tra laïi."), vbOKOnly, VNI("Thoâng baùo")
        txtPass.Text = ""
        txtPass.SetFocus
        iCount = iCount + 1
        Exit Sub
    End If
    If sRight = "Admin" And Len(Trim(sUserSoSanh)) > 0 Then
        txtPass.Text = ""
        txtTenTruyCap.Text = ""
        Me.Hide
    ElseIf Len(Trim(sUserSoSanh)) > 0 Then
        MsgBoxUni VNI("You can only enter data!" & vbCrLf & _
                      "Baïn chæ coù theå nhaäp lieäu maø thoâi."), vbOKOnly, VNI("Thoâng baùo")
        txtPass.Text = ""
        txtTenTruyCap.Text = ""
        Me.Hide
    End If

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

cmdLogIn_Click_Error:
    If bCentralErrorHandler("frmLogIn", "cmdLogIn_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Private Sub cmdThoat_Click()
    End
End Sub

Private Sub UserForm_Initialize()
    On Error GoTo UserForm_Initialize_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    Me.Caption = VNI("Truy caâp vaøo CSDL")
    If Application.ActiveWorkbook.Name <> sWorkbookName Then
        MsgBoxUni VNI("This tool is active only" & vbNewLine & _
                      "The active workbook is StockCountHelper.xls" & vbNewLine & _
                      "Chöùc naêng naøy chæ cho workbook" & vbNewLine & _
                      sWorkbookName), vbOKOnly, VNI("Thoâng baùo")
        With Application
            .Calculation = xlCalculationManual
            .ScreenUpdating = True
        End With
        End    'Do not show form
    End If
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    If bConnected = False Then
        MsgBoxUni VNI("Can not connect to database." & vbNewLine & _
                      "Please contact administrator." & vbNewLine & _
                      "Khoâng theå keát noái vôùi CSDL!" & vbCrLf & _
                      "Xin baïn kieåm tra laïi!"), vbOKOnly, VNI("Thoâng baùo")
        With Application
            .Calculation = xlCalculationManual
            .ScreenUpdating = True
        End With
        End    'Do not show form
    End If
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

UserForm_Initialize_Error:
    If bCentralErrorHandler("frmLogIn", "UserForm_Initialize", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If

End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'Do not allow the user close the form by clicking the X button
    If CloseMode = vbFormControlMenu Then
        Cancel = True
    End If
End Sub

_Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Không cho người dùng đóng form bằng việc click vào nút "X"
_Private Sub UserForm_Initialize()
Các thao tác khi khởi tạo form như:
+Kiểm tra nếu ứng dụng không phải tên theo yêu cầu thì thoát.
+Kiểm tra kết nối CSDL, nếu không được thì cũng không hiện form.
_Private Sub cmdLogIn_Click()
Thực hiện khi người dùng click vào nút cmdLogIn
_Private Sub cmdThoat_Click()
Thực hiện khi người dùng click vào nút cmdThoat

Lê Văn Duyệt
 
Xây dựng form log-in

Trong ví dụ trên tôi có dùng MsgboxUni của Tuân và hàm VNI của Bình OverAC.
Nếu bảng dữ liệu trong Ms Access bạn thiết kế đầy đủ thì các bạn có thể phân quyền ở đây.

Tôi xin giới thiệu các hàm hổ trợ:

_Hàm lấy quyền của người dùng
Mã:
Public Function GetUserRight(ByVal UserName As String) As String
Dim rs         As ADODB.Recordset
Dim strSQL     As String
    On Error GoTo GetUserRight_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    'Then get the data
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        gcnAccess.Open
    End If
    strSQL = "SELECT Right " & _
             "FROM TB_Users " & _
             "WHERE User = '" & UserName & "';"
    Set rs = New ADODB.Recordset
    rs.CursorType = adOpenStatic
    rs.Open strSQL, gcnAccess

    If rs.RecordCount = 0 Then
        GetUserRight = ""
    Else
        GetUserRight = rs.Fields(0)
    End If

ErrorExit:
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If
    If IsNull(rs) Then
        If rs.state = adStateOpen Then
            rs.Close
            Set rs = Nothing
        End If
    End If
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Function

GetUserRight_Error:
    If bCentralErrorHandler("DataUltilities", "GetUserRight", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Function
Chú ý: các bạn có thể hoàn chỉnh hàm này.

_Hàm kiểm tra tên người dùng có tồn tại hay không?

Mã:
Public Function UserExist(UserName As String) As String
Dim rs         As ADODB.Recordset
Dim strSQL     As String

    On Error GoTo UserExist_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    'Then get the data
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        gcnAccess.Open
End if
        strSQL = "SELECT User " & _
                 "FROM TB_Users " & _
                 "WHERE User = '" & UserName & "';"
        Set rs = New ADODB.Recordset
        rs.CursorType = adOpenStatic
        rs.Open strSQL, gcnAccess

        If rs.RecordCount = 0 Then
            UserExist = ""
        Else
            UserExist = rs.Fields(0)
        End If
  
ErrorExit:
    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If
    If IsNull(rs) Then
        If rs.state = adStateOpen Then
            rs.Close
            Set rs = Nothing
        End If
    End If
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True

    End With
    Exit Function

UserExist_Error:
    If bCentralErrorHandler("DataUltilities", "UserExist", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Function

Lê Văn Duyệt
 
Xây dựng form thay đổi mật khẩu

Bây giờ chúng ta xây dựng một form nhằm giúp người dùng có thể tự thay đổi mật khẩu.

thaymatkhau.jpg


Code của form như sau:

Mã:
Option Explicit

Private Sub cmdCancel_Click()
End
End Sub

Private Sub cmdChangePw_Click()
Dim sUserName  As String
Dim sPass As String, sPassComp As String, sNewPass As String
Dim rs As ADODB.Recordset, sSQL As String
    On Error GoTo cmdChangePw_Click_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    sUserName = Me.txtUserName.Text
    If UserExist(sUserName) = "" Then
        MsgBoxUni VNI("Teân truy caäp khoâng ñuùng." & vbCrLf & _
                      "Baïn phaûi truy caäp tröôùc khi" & vbCrLf & _
                      "söû duïng coâng cuï naøy."), vbOKOnly, VNI("Thoâng baùo")
        With Application
            .Calculation = xlCalculationManual
            .ScreenUpdating = True
        End With
        Exit Sub
    End If

    Call ConnectToDatabase
    If bConnected = False Then
        MsgBoxUni VNI("Can not connect to database." & vbNewLine & _
                      "Please contact administrator." & vbNewLine & _
                      "Khoâng theå keát noái vôùi CSDL!" & vbCrLf & _
                      "Xin baïn kieåm tra laïi!"), vbOKOnly, VNI("Thoâng baùo")
        With Application
            .Calculation = xlCalculationManual
            .ScreenUpdating = True
        End With
        End    'Do not show form
    End If
    sNewPass = Me.txtNewPass.Text
    sPassComp = Me.txtOldPass.Text
    sPass = GetUserPassword(sUserName)
    If sPass = sPassComp Then
        sSQL = "UPDATE TB_Users " & _
               "SET  Pass='" & sNewPass & "' " & _
               "WHERE User='" & sUserName & "' ;"
        Set rs = New ADODB.Recordset
        If gcnAccess.state = ObjectStateEnum.adStateClosed Then
            gcnAccess.Open
        End If
        rs.Open sSQL, gcnAccess, adOpenKeyset, adLockOptimistic
        MsgBoxUni VNI("Baïn ñaõ caäp nhaät maät khaåu thaønh coâng."), vbOKOnly, VNI("Thoâng baùo")
        Set rs = Nothing
        With Application
            .Calculation = xlCalculationManual
            .ScreenUpdating = True
        End With
        End
    Else
        MsgBoxUni VNI("Xin nhaäp laïi maät khaåu."), vbOKOnly, VNI("Thoâng baùo")
        Me.txtOldPass.Text = ""
        Me.txtNewPass.Text = ""
        Me.txtOldPass.SetFocus
    End If

ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

cmdChangePw_Click_Error:
    MsgBoxUni VNI("Baïn ñaõ caäp nhaät maät khaåu khoâng thaønh coâng." & vbCrLf & _
                  "Xin kieåm tra laïi!"), vbOKOnly, VNI("Thoâng baùo")
    If bCentralErrorHandler("frmChangePw", "cmdChangePw_Click", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If

End Sub

Private Sub UserForm_Initialize()
Dim sUserName  As String
    On Error GoTo UserForm_Initialize_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    Me.Caption = VNI("Thay ñoåi maät khaåu")
    If Application.ActiveWorkbook.Name <> sWorkbookName Then
        MsgBoxUni VNI("This tool is active only" & vbNewLine & _
                      "The active workbook is " & sWorkbookName & vbNewLine & _
                      "Chöùc naêng naøy chæ cho workbook" & vbNewLine & _
                      sWorkbookName), vbOKOnly, VNI("Thoâng baùo")
        With Application
            .Calculation = xlCalculationManual
            .ScreenUpdating = True
        End With
        End    'Do not show form
    End If
    Call ConnectToDatabase
    If bConnected = False Then
        MsgBoxUni VNI("Can not connect to database." & vbNewLine & _
                      "Please contact administrator." & vbNewLine & _
                      "Khoâng theå keát noái vôùi CSDL!" & vbCrLf & _
                      "Xin baïn kieåm tra laïi!"), vbOKOnly, VNI("Thoâng baùo")
        With Application
            .Calculation = xlCalculationManual
            .ScreenUpdating = True
        End With
        End    'Do not show form
    End If

    sUserName = Application.Range("UserName").Value
    If Len(Trim(sUserName)) = 0 Then
        MsgBoxUni VNI("Baïn phaûi nhaäp tröôùc khi söû duïng."), vbOKOnly, VNI("Thoâng baùo")
        With Application
            .Calculation = xlCalculationManual
            .ScreenUpdating = True
        End With
        End    'Do not show form
    End If
    Me.txtUserName.Text = sUserName
ErrorExit:
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

UserForm_Initialize_Error:
    If bCentralErrorHandler("frmChangePw", "UserForm_Initialize", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If

End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'Do not allow the user close the form by clicking the X button
    If CloseMode = vbFormControlMenu Then
        Cancel = True
    End If
End Sub

Chúng ta có các thủ tục sau:
_Private Sub cmdCancel_Click()
Khi người dùng chọn nút này thì đóng form và thoát
_Private Sub cmdChangePw_Click()
Thủ tục này nhằm:
+Kiểm tra nếu tên người dùng không tồn tại thì cũng thoát.
+Kết nối với CSDL: nếu không kết nối được thì cũng thoát.
+So sánh Password cũ với password trong hệ thống. Nếu trùng thì chúng ta mới tiến hành cập nhật password mới. Password mới cho phép là chuổi rổng.

_Private Sub UserForm_Initialize()
Tương tự như phần giải thích đối với form truy nhập CSDL.
_Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)

Như vậy ứng dụng của chúng ta sẽ linh động hơn.

Lê Văn Duyệt
 
Mã hóa các thông tin nhạy cảm

Với ví dụ trên các bạn sẽ thấy rằng việc lưu thông tin nhạy cảm như:
_Tên người truy nhập.
_Mật khẩu.
Chúng ta không hề có mã hóa.
Do đó nếu các bạn có yêu cầu cao hơn chúng ta phải dùng các thuật toán mã hóa.

Đối với các ứng dụng như thế này, thông thường sau khi thao tác trích rút dữ liệu chúng ta có một recordset lấy về. Chúng ta muốn xuất dữ liệu từ đây ra MS.Excel thì sao? Chúng ta có thể dùng thủ tục sau:

Mã:
Sub RecordsetToRange(rst As ADODB.Recordset, wsName As String)

Dim xlWb       As Workbook
Dim xlWs       As Worksheet
Dim fldCount As Long, recCount As Long
Dim iCol As Integer, iRow As Long
Dim recArray   As Variant

If rst Is Nothing Then    'i.e You did not initial the variance
        MsgBoxUni VNI("Baïn chöa khôûi taïo recordset." & _
                      "Baïn xem laïi!"), vbOKOnly, VNI("Thoâng baùo")
        Exit Sub
    End If
    If rst.RecordCount = 0 Then
        MsgBoxUni VNI("Khoâng coù döõ lieäu ñeå xuaát"), vbOKOnly, VNI("Thoâng baùo")
        Exit Sub
    Else
        MsgBoxUni VNI("Soá record laø: " & rst.RecordCount), vbOKOnly, VNI("Thoâng baùo")
    End If
    'Check the worksheet
    If Not SheetExists(wsName) Then
        MsgBoxUni VNI("Worksheet khoâng toàn taïi." & vbCrLf & _
                      "Xin kieåm tra laïi."), vbOKOnly, VNI("Thoâng baùo")
        Exit Sub
    End If

    Set xlWb = Application.ThisWorkbook
    Set xlWs = xlWb.Worksheets(wsName)
    'Delete before export data
    xlWs.Cells.Clear    'Delete data on this worksheet first
    ' Copy field names to the first row of the worksheet
    fldCount = rst.Fields.Count
    For iCol = 1 To fldCount
        xlWs.Cells(1, iCol).Value = rst.Fields(iCol - 1).Name
    Next

    ' Check version of Excel
    If Val(Mid(Application.Version, 1, InStr(1, Application.Version, ".") - 1)) > 8 Then
        'EXCEL 2000,2002,2003, or 2007: Use CopyFromRecordset

        ' Copy the recordset to the worksheet, starting in cell A2
        xlWs.Cells(2, 1).CopyFromRecordset rst
        'Note: CopyFromRecordset will fail if the recordset
        'contains an OLE object field or array data such
        'as hierarchical recordsets

    Else
        'EXCEL 97 or earlier: Use GetRows then copy array to Excel

        ' Copy recordset to an array
        recArray = rst.GetRows
        'Note: GetRows returns a 0-based array where the first
        'dimension contains fields and the second dimension
        'contains records. We will transpose this array so that
        'the first dimension contains records, allowing the
        'data to appears properly when copied to Excel

        ' Determine number of records

        recCount = UBound(recArray, 2) + 1    '+ 1 since 0-based array


        ' Check the array for contents that are not valid when
        ' copying the array to an Excel worksheet
        For iCol = 0 To fldCount - 1
            For iRow = 0 To recCount - 1
                ' Take care of Date fields
                If IsDate(recArray(iCol, iRow)) Then
                    recArray(iCol, iRow) = Format(recArray(iCol, iRow))
                    ' Take care of OLE object fields or array fields
                ElseIf IsArray(recArray(iCol, iRow)) Then
                    recArray(iCol, iRow) = "Array Field"
                End If
            Next iRow    'next record
        Next iCol    'next field

        ' Transpose and Copy the array to the worksheet,
        ' starting in cell A2
        xlWs.Cells(2, 1).Resize(recCount, fldCount).Value = _
        TransposeDim(recArray)
    End If

    ' Auto-fit the column widths and row heights

    With xlWs.Cells
        .Columns.AutoFit
        .Rows.AutoFit
    End With
    ' Close ADO objects
    rst.Close
    Set rst = Nothing
    ' Release Excel references
    Set xlWs = Nothing
    Set xlWb = Nothing

End Sub

Đoạn mã trên tôi tham khảo tại trang web của M$.
Chúc các bạn cuối tuần vui vẻ.

Xin các bạn góp ý về levanduyet@yahoo.com

Lê Văn Duyệt
 
Đối với ứng dụng chúng ta viết, chúng ta sẽ thường gặp :
1_Xuất dữ liệu ban đầu vào CSDL.
2_Trích rút từ CSDL đưa ra Excel.

+Đối với (1), thông thường chúng ta chỉ làm một lần.
Đối với thao tác này chúng ta phải cẩn thận kiểm tra dữ liệu thô trước. Ví dụ như các ràng buộc chiều dài của mã sản phẩm, chiều dài của đơn vị tính,...vv...
Sau khi kiểm tra xong chúng ta tiến hành xuất dữ liệu vào bảng trong CSDL.
Tôi xin đưa một ví dụ sau:

Mã:
Sub ExportProData(Optional bDeleteTable As Boolean)
' exports data from the active worksheet to a table in an Access database
' TB_TEMPMAIN
Dim rs As ADODB.Recordset, R As Long
Dim sSQL As String, lRecordCount As Long
Dim lBbQty As Long, lRecQty As Long, lIssQty As Long, lQty As Long
Dim vDate      As Variant
    'Kiem tra xem co the connect vao file du lieu khong
    'Neu khong duoc thi thong bao va Thoat
    On Error GoTo ExportProData_Error
    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With
    If IsMissing(bDeleteTable) Then bDeleteTable = False
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        Call ConnectToDatabase
    End If
    If bConnected = False Then
        MsgBoxUni VNI("Khoâng theå keát noái vôùi CSDL!" & vbCrLf & _
                      "Xin baïn kieåm traû laïi!" & vbCrLf & _
                      "Can not connect to the Database!" & vbCrLf & _
                      "Pls, contact to your Administrator!"), vbOKOnly, VNI("Thoâng baùo")
        Exit Sub
    End If
    sTableInDBName = "TB_TEMPMAIN"
    'Delete the data first
    If bDeleteTable Then
        Call DeleteAll(sTableInDBName)
    End If
    If gcnAccess.state = ObjectStateEnum.adStateClosed Then
        gcnAccess.Open
    End If

    Set rs = New ADODB.Recordset

    rs.Open sTableInDBName, gcnAccess, adOpenKeyset, adLockOptimistic, adCmdTable
    R = 5    ' the start row in the worksheet
    '************************************************************************************
    'The header format must be
    'Doc_Date | Item_Code | UoM | Description | Bb | Rec | Iss | Doc_Ref
    '************************************************************************************
    sTenWorkbookThaoTac = Application.ThisWorkbook.Name
    sTenWorksheetThaoTac = "PRO_DATA"

    Do While Len(Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("A" & R).Formula) > 0
        ' repeat until first empty cell in column A
        With rs
            .AddNew    ' create a new record
            ' add values to each field in the record
            .Fields("dTsDate") = Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("A" & R).Value
            .Fields("sTsNo") = Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("B" & R).Value
            .Fields("sTsUoM") = Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("C" & R).Value
            .Fields("sTsDes") = Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("D" & R).Value
            lBbQty = (1) * Val(Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("E" & R).Value)
            lRecQty = (1) * Val(Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("F" & R).Value)
            lIssQty = (-1) * Val(Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("G" & R).Value)
            lQty = IIf(lBbQty <> 0, lBbQty, IIf(lRecQty <> 0, lRecQty, lIssQty))
            .Fields("nTsQty") = lQty
            .Fields("sTsRef") = Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("H" & R).Value
            vDate = Application.ThisWorkbook.Worksheets(sTenWorksheetThaoTac).Range("A" & R).Value
            .Fields("nDay") = Day(vDate)
            .Fields("nMonth") = Month(vDate)
            .Fields("nYear") = Year(vDate)
            .Fields("sTsLoc") = "FGD"
            .Update    ' stores the new record

        End With
        R = R + 1    ' next row
    Loop
    rs.Close
    MsgBoxUni VNI("Baïn ñaõ xuaát thaønh coâng!" & vbCrLf & _
                  "You have exported successful !"), vbOKOnly, VNI("Thoâng baùo")
    

ErrorExit:

    If gcnAccess.state = ObjectStateEnum.adStateOpen Then
        gcnAccess.Close
    End If

    With Application
        .Calculation = xlCalculationManual
        .ScreenUpdating = True
    End With
    Exit Sub

ExportProData_Error:
    If Err.Number <> 0 Then
        MsgBoxUni VNI("Baïn ñaõ xuaát döõ lieäu khoâng thaønh coâng." & vbCrLf & _
                      "Xin baïn kieåm tra laïi."), vbOKOnly, VNI("Thoâng baùo")
    End If
    If bCentralErrorHandler("DataUltilities", "ExportProData", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

Trong ví dụ trên giả sử rằng chúng ta đã kiểm tra dữ liệu trước khi thực hiện thủ tục này.
Thủ tục này sẽ xóa dữ liệu trong bảng dữ liệu, rồi xuất dữ liệu từ worksheet
PRO_DATA vào bảng dữ liệu
Mã:
sTableInDBName = "TB_TEMPMAIN"
Tiêu đề của bảng dữ liệu trong Ms Excel như sau:

Mã:
Doc_Date | Item_Code | UoM | Description | Bb | Rec | Iss | Doc_Ref

Chúng ta cũng có thể viết một hàm để kiểm tra các tiêu đề này.

+Việc trích rút dữ liệu từ các bảng dữ liệu ra, tương tự như add-in A-Excel

Tôi xin giới thiệu một hàm để làm việc này

Mã:
' (c) Copyright IBM Corp. 2003  All rights reserved.
Function QueryRecords(ByRef adoConnection As ADODB.Connection, ByVal sSQL As String) As ADODB.Recordset
'Create ADO objects
Dim adoCommand As ADODB.Command
Dim adoRecordSet As ADODB.Recordset

    On Error GoTo QueryRecords_ErrHandler

    Set adoCommand = New ADODB.Command

    With adoCommand
        .CommandType = adCmdText
        .ActiveConnection = adoConnection
        .CommandText = sSQL
    End With

    'Create record set
    Set adoRecordSet = New ADODB.Recordset
    adoRecordSet.Open adoCommand, , adOpenStatic, adLockOptimistic

    Set QueryRecords = adoRecordSet

    Set adoRecordSet = Nothing
    Set adoCommand = Nothing
    Exit Function

QueryRecords_ErrHandler:
    Call ShowAllErrors(adoConnection)
    Set QueryRecords = Nothing
End Function

Sub ShowAllErrors(ByRef adoConnection As ADODB.Connection)
    If adoConnection.Errors.Count > 0 Then
        MsgBox "Error code:  " & adoConnection.Errors(0).Number & vbNewLine & _
               "Description: " & adoConnection.Errors(0).Description & vbNewLine & _
               "Source:      " & adoConnection.Errors(0).Source, _
               vbOKOnly + vbCritical
        adoConnection.Errors.Clear
    Else
        MsgBox "Error code:  " & Err.Number & vbNewLine & _
               "Description: " & Err.Description & vbNewLine & _
               "Source:      " & Err.Source, _
               vbOKOnly + vbCritical
        Err.Clear
    End If
End Sub

Thủ tục:
Mã:
Function QueryRecords(ByRef adoConnection As ADODB.Connection, ByVal sSQL As String) As ADODB.Recordset
Sẽ trả về Recordset.
Tham số truyền vào là chuổi SQL.

Hàm trên tôi sửa lại từ code của IBM.

Sau khi các bạn lấy được Recordset về thì dùng thủ tục tôi đã giới thiệu ở trên để xuất ra MS Excel.

Lê Văn Duyệt.
 
Nhân tiện đây tôi cũng xin giới thiệu với các bạn :
+Hàm
Mã:
Function RecordsetToHTMLTable(rs As ADODB.Recordset, _
                              ByVal TableAttribs As String, Optional ByVal NullValues As String = _
                                                            "&nbsp;", Optional ByVal ShowFieldNames As Boolean, _
                              Optional ByVal IncludeWhiteSpace As Boolean) As String

Chuyển recordset sang bảng HTML

Mã:
' Create a HTML table from a recordset
'
' set the TableAttribs argument to a suitable value
'     (eg "BORDER=1") to modify the table's standard layout
' you should omit the NullValues argument if you want that null
'     values are displayed in empty cells
' set the ShowFieldNames to True to display field names in boldface
' set the IncludeWhiteSpace argument to True if you want to produce
'     a longer and less efficient (but more readable) output

Function RecordsetToHTMLTable(rs As ADODB.Recordset, _
                              ByVal TableAttribs As String, Optional ByVal NullValues As String = _
                                                            "&nbsp;", Optional ByVal ShowFieldNames As Boolean, _
                              Optional ByVal IncludeWhiteSpace As Boolean) As String
Dim res        As String
Dim fld        As ADODB.Field
Dim tmp        As String
Dim lf As String, tb As String

    ' fill these variables only if spaces are to be kept
    If IncludeWhiteSpace Then
        lf = vbCrLf
        tb = vbTab
    End If

    ' prepare the <TABLE> tag
    res = "<TABLE " & TableAttribs & ">" & lf

    ' show field names, if required
    If ShowFieldNames Then
        res = res & tb & "<HEAD>" & lf
        For Each fld In rs.Fields
            res = res & tb & tb & "<TD><B>" & fld.Name & "</B></TD>" & lf
        Next
        res = res & tb & "</HEAD>" & lf
    End If

    ' get all the records in a semi-formatted string
    tmp = rs.GetString(, , "</TD>" & lf & tb & tb & "<TD>", _
                       "</TD>" & lf & tb & "</TR>" & lf & tb & "<TR>" & lf & tb & tb & "<TD>", _
                       NullValues)
    ' strip what has been appended to the last cell of the last row
    tmp = Left$(tmp, Len(tmp) - Len(lf & tb & "<TR>" & lf & tb & tb & "<TD>"))

    ' add opening tags to the first cell of the first row of the table
    ' and complete the table
    RecordsetToHTMLTable = res & tb & "<TR>" & lf & tb & tb & "<TD>" & tmp & lf _
                           & "</TABLE>"

End Function

+Thủ tục
Mã:
Public Sub FillListView(ByRef sListView As Listview, ByRef sRecordSource As Recordset, ByVal sNumOfFields As Byte, ByVal sNumIco As Byte, ByVal with_num As Boolean, _
                        ByVal show_first_rec As Boolean, Optional srcHiddenField As String)

Mã:
'*****************************************************************************************
'This code is also available in .NET version with ADO.NET
'Procedure used to fill list view
'Programmer: Philip V. Naparan   E-mail: philipnaparan@yahoo.com
'*****************************************************************************************
Public Sub FillListView(ByRef sListView As Listview, ByRef sRecordSource As Recordset, ByVal sNumOfFields As Byte, ByVal sNumIco As Byte, ByVal with_num As Boolean, _
                        ByVal show_first_rec As Boolean, Optional srcHiddenField As String)
Dim X          As Variant
Dim i          As Byte
    On Error Resume Next
    sListView.ListItems.Clear
    If sRecordSource.RecordCount < 1 Then Exit Sub
    sRecordSource.MoveFirst
    Do While Not sRecordSource.EOF
        If with_num = True Then
            Set X = sListView.ListItems.ADD(, , sRecordSource.AbsolutePosition, sNumIco, sNumIco)
        Else
            Set X = sListView.ListItems.ADD(, , "" & sRecordSource.Fields(0), sNumIco, sNumIco)
        End If
        If srcHiddenField <> "" Then X.Tag = sRecordSource.Fields(srcHiddenField)
        For i = 1 To sNumOfFields - 1
            If show_first_rec = True Then
                If with_num = True Then
                    If sRecordSource.Fields(CInt(i) - 1).Type = adDouble Then
                        X.SubItems(i) = FormatRS(sRecordSource.Fields(CInt(i) - 1))
                    Else
                        X.SubItems(i) = "" & FormatRS(sRecordSource.Fields(CInt(i) - 1))
                    End If
                Else
                    If sRecordSource.Fields(CInt(i)).Type = adDouble Then
                        X.SubItems(i) = FormatRS(sRecordSource.Fields(CInt(i)))
                    Else
                        X.SubItems(i) = "" & FormatRS(sRecordSource.Fields(CInt(i)))
                    End If
                End If
            Else
                X.SubItems(i) = "" & FormatRS(sRecordSource.Fields(CInt(i) + 1))
            End If
        Next i
        sRecordSource.MoveNext
    Loop
    i = 0
    Set X = Nothing
End Sub

Thủ tục điền recordset sang Listview

+Thủ tục nén CSDL MS Access
Mã:
Public Sub CompactAccDB()

Mã:
Option Explicit

Declare Function GetTempFileName Lib "kernel32" _
                                 Alias "GetTempFileNameA" (ByVal lpszPath As String, _
                                                           ByVal lpPrefixString As String, ByVal wUnique As Long, _
                                                           ByVal lpTempFileName As String) As Long

Declare Function FindExecutable Lib "shell32.dll" _
                                Alias "FindExecutableA" (ByVal lpFile As String, _
                                                         ByVal lpDirectory As String, ByVal lpResult As String) As Long



'---------------------------------------------------------------------------------------
' Procedure : CompactAccDB
' DateTime  : 13/05/2007 11:07
' Author    : levanduyet
' Purpose   : Compact the Access Database
' References: Microsoft Access 11.0 Object Library
'             C:\Program Files\Microsoft Office\OFFICE11\MSACC.OLB
'---------------------------------------------------------------------------------------
'
Public Sub CompactAccDB()
Dim sFilePath  As String
Dim sOldName As String, sNewName As String
Dim sPathOnly  As String
    On Error GoTo CompactAccDB_Error

    sFilePath = Application.GetOpenFilename("Access Files,*.mdb,All Files,*.*", 1, "Pls chose the file", , False)
    If Len(sFilePath) < 6 Then
        MsgBoxUni VNI("Pls, re-check your file!" & vbCrLf & _
                      "Xin kieåm tra laïi ñöôøng daãn!"), vbOKOnly, VNI("Thoâng baùo")
        Exit Sub
    Else
        sPathOnly = PathOnly(sFilePath)
        If Right$(sPathOnly, 1) <> "\" Then sPathOnly = sPathOnly & "\"
        sOldName = sPathOnly & FileNameOnly(sFilePath)
        sNewName = sPathOnly & "DB2.mdb"
        'Start to compact
        If sOldName <> "" Then
            DoEvents
            DBEngine.CompactDatabase sOldName, sNewName
            Kill sOldName
            Name sNewName As sOldName
            MsgBox "You have compact the database file", vbOKOnly, "Inf"
        End If
    End If
    Exit Sub

ErrorExit:
    MsgBox "Can not compact the file!" & vbCrLf & _
           "Pls check the reference to " & vbCrLf & _
           "Microsoft Access 11.0 Object Library(MSACC.OLB)", vbOKOnly, "Inf"

    Exit Sub

CompactAccDB_Error:
    If bCentralErrorHandler("DataUltilities", "CompactAccDB", , False) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If

End Sub

Chúc các bạn cuối tuần vui vẻ.

Lê Văn Duyệt
 
Class module mã hóa

Đối với một số ứng dụng các dữ liệu nhạy cảm chúng ta cần phải mã hóa.
Tôi xin giới thiệu với các bạn một class module sau:
Mã:
Option Explicit
' Comersus 5.0x Sophisticated Cart
' Developed by Rodrigo S. Alhadeff for Comersus Open Technologies
' USA - 2003
' Open Source License can be found at documentation/readme.txt
' http://www.comersus.com
' Details: RC4 and DES redirection encryption functions
' This script performs 'RC4' Stream Encryption (Based on what is widely thought to be RSA's RC4 algorithm. It produces output streams that are identical to the commercial products). This script is Copyright © 1999 by Mike Shaffer ALL RIGHTS RESERVED WORLDWIDE

Private sBox(255)
Private rc4Key(255)
Private pEncryptionMethod As String
Private Password As String

Property Let CryptoKey(CryptoPassword As String)
    Password = CryptoPassword
End Property

Property Get Encrypt(PlainText As String) As String
    If pEncryptionMethod = "DES" Then
        Encrypt = DESEncrypt(PlainText, Password)
    Else
        Encrypt = RC4EnCryptASC(PlainText, Password)
    End If
End Property

Property Get Decrypt(PlainText As String) As String
    If pEncryptionMethod = "DES" Then
        Decrypt = DESDecrypt(PlainText, Password)
    Else
        Decrypt = RC4DeCryptASC(PlainText, Password)
    End If
End Property

Function DESEncrypt(i1 As String, i2 As String)

End Function

Private Function DESDecrypt(i1 As String, i2 As String)

End Function
   
Private Sub RC4Initialize(strPwd As String)
    ' this routine called by EnDeCrypt function. Initializes the sbox and the key array
    Dim tempSwap, a, b
    Dim intLength
    ' get length of the key
    intLength = Len(strPwd)
    
    ' iterate through all characters contained in key repeating number of characters is 255
    For a = 0 To 255
       ' load ANSI for each char contained in the key
       rc4Key(a) = Asc(Mid(strPwd, (a Mod intLength) + 1, 1))
       ' load numbers from 0 to 255
       sBox(a) = a
    Next
    
    b = 0
    ' iterate through arrays
    For a = 0 To 255
       b = (b + sBox(a) + rc4Key(a)) Mod 256
       tempSwap = sBox(a)
       sBox(a) = sBox(b)
       sBox(b) = tempSwap
    Next
End Sub
   
Private Function EnDeCrypt(plaintxt As String, psw As String)
   Dim temp, a, i, j, k, cipherby, cipher
   i = 0
   j = 0

   RC4Initialize psw

   For a = 1 To Len(plaintxt)
      i = (i + 1) Mod 256
      j = (j + sBox(i)) Mod 256
      temp = sBox(i)
      sBox(i) = sBox(j)
      sBox(j) = temp

      k = sBox((sBox(i) + sBox(j)) Mod 256)

      cipherby = Asc(Mid(plaintxt, a, 1)) Xor k
      cipher = cipher & Chr(cipherby)
   Next

   EnDeCrypt = cipher
End Function

Private Function RC4EnCryptASC(plaintxt As String, psw As String)
   Dim temp, a, i, j, k, cipherby, cipher

   i = 0
   j = 0

   RC4Initialize psw

   For a = 1 To Len(plaintxt)
      i = (i + 1) Mod 256
      j = (j + sBox(i)) Mod 256
      temp = sBox(i)
      sBox(i) = sBox(j)
      sBox(j) = temp

      k = sBox((sBox(i) + sBox(j)) Mod 256)

      cipherby = Asc(Mid(plaintxt, a, 1)) Xor k
      cipher = cipher & "|" & cipherby
   Next
   
   RC4EnCryptASC = cipher
   
End Function

Private Function RC4DeCryptASC(plaintxt As String, psw As String)
   plaintxt = GetRC4String(plaintxt)
   Dim temp, a, i, j, k, cipherby, cipher
   i = 0
   j = 0
   Dim arrayEncrypted
   RC4Initialize psw
    For a = 1 To Len(plaintxt)
      i = (i + 1) Mod 256
      j = (j + sBox(i)) Mod 256
      temp = sBox(i)
      sBox(i) = sBox(j)
      sBox(j) = temp

      k = sBox((sBox(i) + sBox(j)) Mod 256)

      cipherby = Asc(Mid(plaintxt, a, 1)) Xor k
      cipher = cipher & Chr(cipherby)
   Next
   RC4DeCryptASC = cipher
End Function
   
Private Function GetRC4String(iString As String) As String
    Dim i As Long
    Dim j As Long
    ' remove dash
    Dim iStr As String
    Dim iRet As String
    iStr = iString
    While InStr(iStr, "|") <> 0
        i = InStr(1, iStr, "|")
        j = InStr(2, iStr, "|")
        If j = 0 Then
            iRet = iRet & Chr(Val(Mid(iStr, i + 1)))
            iStr = Mid(iStr, i + 1)
        Else
            iRet = iRet & Chr(Val(Mid(iStr, i + 1, j - i - 1)))
            iStr = Mid(iStr, j)
        End If
    Wend
    GetRC4String = iRet
End Function

Private Sub Class_Initialize()
    pEncryptionMethod = "RC4"
End Sub

Để sử dụng class trên các bạn hãy tạo ra một module, chép thủ tục này vào module đó và thử thực hiện thủ tục này xem sao?

Mã:
Sub Test()
    Dim objCrypto As New cls_Crypto
    Dim sTenNguoiDung As String, sMatKhau As String
    Dim sChuoiMaHoa As String, sChuoiSauKhiGiaiMa As String
    sTenNguoiDung = "Le Van Duyet": sMatKhau = "bongluyen"
    'Lay ten nguoi dung lam chia khoa de ma hoa
    objCrypto.CryptoKey = sTenNguoiDung
    'Ma hoa mat khau
    sChuoiMaHoa = objCrypto.Encrypt(sMatKhau)

    'Thong bao
    MsgBox "Ten nguoi dung la: " & sTenNguoiDung & vbCrLf & _
           "Password truoc khi ma hoa la: " & sMatKhau & vbCrLf & _
           "Password sau khi ma hoa la: " & sChuoiMaHoa, vbOKOnly, "Thong bao"
    sChuoiSauKhiGiaiMa = objCrypto.Decrypt(sChuoiMaHoa)
    MsgBox "Password sau khi duoc giai ma la: " & sChuoiSauKhiGiaiMa, vbOKOnly, "Thong bao"

End Sub

Lê Văn Duyệt
 

File đính kèm

Chương trình trợ giúp cho việc kiểm kho

Trong SAP cũng có một module trợ giúp cho việc kiểm kho. Nhưng theo tôi biết giá rất cao.
Qua kinh nghiệm làm việc tôi thấy đối với công ty lớn, việc kiểm kho sẽ mất khá nhiều thời gian (ít nhất 2 ngày). Chính vì lý do này tôi viết một công cụ, cũng dạng kết hợp Access + Excel trợ giúp cho việc kiểm kho này.
Ưu điểm của công cụ này:
_ Số nhân viên nhập liệu tăng.
_ Rút ngắn được thời gian nhập liệu. (Vì mã sản phẩm là 10 ký tự, nhưng do quy định của người dùng, bắt đầu bằng 22=> là thành phẩm, bắt đầu bằng 21=> bán thành phẩm, bắt đầu bằng 20=> nguyên vật liệu. chính vì vậy tại ô Default text sẽ giúp được việc này)
_ Báo cáo có thể lấy ra tại từng thời điểm.
_ Chỉnh sửa số liệu nếu nhập sai cũng dễ dàng hơn.
_ Thêm, xóa nhân viên nhập liệu dễ dàng.
Nhược điểm:
_ Phải thiết lập DSN

Các thông tin cần cho nhập liệu:
_ Mã hàng
_ Số lượng
_ Số thẻ kho
_ Tình trạng sản phẩm (Hư, chờ sửa, tốt)
_ Ghi chú: có thể dùng làm Location, tức là vị trí để hàng hiện tại

Các bạn xem màn hình nhập liệu:

KiemKho1.jpg


Đầu tiên khi vào chương trình các bạn cũng phải Log-in vào với User Name, Pass được người có quyền Admin cung cấp.
Sau đó bạn mở form nhập liệu giống hình bên trên.
_Thao tác nhập liệu
Các bạn nhập vào mã sản phẩm, chú ý để thao tác này nhanh hơn các bạn cần phải thiết lập giá trị tại ô Default text.
Ví dụ: thông thường mã sản phẩm bắt đầu bằng 220000 như vậy bạn sẽ nhập vào ô Default text này. Với việc làm này các bạn tiết kiệm được thời gian nhập 6 chữ số đầu của mã sản phẩm.
Mỗi khi các bạn nhập mã sản phẩm vào, Listview bên dưới sẽ tự động cuộn lên đến mã các bạn cần. Các bạn không cần phải nhập hết tất cả 10 chữ số vào. Nếu các bạn thấy cuộn lên đúng với mã của các bạn rồi thì chỉ việc nhấn nút Enter.
Tương tự nhập tiếp cho các trường khác. Để chuyển từ Textbox này qua Textbox khác các bạn chỉ việc nhấn Enter, hoặc Tab.
Sau khi nhập đầy đủ số liệu thì nhấn nút Điền tạm số liệu vào.
Lúc này dữ liệu các bạn nhập vào sẽ đưa vào Listview bên dưới.
Nhập đến lúc nào, các bạn muốn đưa dữ liệu này vào CSDL thì các bạn có thể nhấn nút Nhập vào CSDL.

Trong công cụ này nếu bạn được đặt quyền Admin thì các bạn có thể Lock CSDL lại để sửa chữa. Trong trường hợp các bạn đang Lock CSDL thì việc nhập vào CSDL ở trên sẽ nhận được câu thông báo.

Sau khi nhập xong các bạn nhấn nút Thoát

Các chức năng chính được thể hiện ở Menu sau:

KiemKho2.jpg

Chú ý: khi mở các form nhập liệu thì dữ liệu bảng mã sản phẩm sẽ được đặt trên Excel. Chính vì vậy nếu cần cập nhật người có quyền Admin sẽ làm việc này và sau đó cập nhật vào bảng dữ liệu trong Access. Lúc này các người dùng còn lại có thể cập nhật từ bảng dữ liệu trong Access (vừa mới cập nhật ở trên) về lại Workbook của mình. Đó là hai chức năng kế tiếp trong bảng menu ở trên.
Chức năng số 5: để xuất dữ liệu kiểm kho ra Excel.
Chức năng số 6: để sửa dữ liệu (dựa vào thẻ kho/Mã sản phẩm) kiểm kho do sai xót trong quá trình nhập liệu.
Các chức năng còn lại tôi không cần giải thích các bạn cũng dễ dàng hiểu nó.

Các bạn có thể tham khảo file đính kèm, không password.
Các bạn có thể phát triển ý tưởng theo ý của mình.

Chú ý: việc tạo DSN tương tự như hướng dẫn tại đây.. Nhưng chú ý chọn file Access KiemKho mà tôi upload lên đây.
Khi mở file StockCountHelper.xls các bạn cần nhập vào
User: Le Van Duyet
Pass: bạn không cần nhập

Lê Văn Duyệt
 

File đính kèm

Lần chỉnh sửa cuối:

Bài viết mới nhất

Back
Top Bottom