Viết một hàm VBA "chuẩn"

Liên hệ QC

hutonline

Thành viên mới
Tham gia
25/7/07
Bài viết
22
Được thích
7
bác tuanuni nói thì điều đó ai cũng muốn làm nhưng vấn đề nhiều khi ta chỉ muốn đạt được mục đích nhỏ trước mắt mà thôi chứ các trường hợp khác nó nhiều và lớn lao quá. Nếu 1 fần mềm thương mại mà cứ làm như trước giờ ta vẫn làm thì làm sao mà bán được, thế mới là bác BIN chứ
 
Tớ cũng nói rồi mà, phần lớn chúng ta quan tới tới mục định nhất định chứ chưa bao quát hết tình huống. Tớ viết bài này lên đây để những chúng ta cùng nâng cao trình độ về lập trình. Đặc biệt không có ý khoe khoang hay chê bai ai (vì bản thân mình chưa là gì).

Khi chúng ta viết ứng ụng trên VB, do bản chất của ngôn ngữ đơn giản, dễ tiếp cận (nhất là với giớp không chuyên) đã giúp và làm cho chúng ta "đơn giản" quá nhiều trong khi viết ứng dụng, vì sự dễ tiếp cận của VB phần nào khiến cho những người mới tiếp cận dễ dẫn đến tính ẩu hoặc cẩu thả khi lập trình. Trong ngôn ngữ C/C++, Pascal (ngôn ngữ tạo ra hệ điều hành, Pascal tạo ra HĐH Mac) thì rất nghiêm ngặt, nếu không khai báo biến thì không thể sử dụng, kiểu dữ liệu chặt chẽ, C/C++ thì lại phân biệt chữ thường chữ hoa,...Nếu ai đã thử nghiệm qua, ban đầu thực sự ngán. Nhưng các chuyên gia nói đó là các ngôn ngữ cho chuyên gia còn VB chưa phải nơi thể hiện đẳng cấp thực sự (về mặt ngôn ngữ).
Điều gì xảy ra nếu chúng ta viết ứng dụng gọi là làm ẩu?
- Về mặt kỹ thuật, làm dư thừa tài nguyên, tràn bộ nhớ, treo máy. Nếu các bạn lập trình trên các ngôn ngữ khác, tạo các DLL dùng chung thì cần cẩn thận từng dòng lệnh. Chúng ta vẫn hay than phiền Excel chạy chậm khi dữ liệu lớn, đó một phần chính là chúng ta làm chưa khoa học+do ẩu dẫn đến lãng phí tài nguyên, bộ nhớ đầy ->chạy chậm. Người bị bệnh cũng vậy, khi phát hiện ra bệnh thường là khi bệnh đã bị nặng rồi, nếu theo dõi điều trị từ trước chắc đã đỡ hơn nhiều.

- Về mặt nghiệp vụ, đây mới là cái thực sự quan trong nhất. Khi chúng ta cần tính một khuản tiền cho N người, với các điều kiện ràng buộc, các tình huống khác nhau. Nếu ít nhất một tình huống ta chưa tìm ra nguy cơ tiềm ẩn về sự sai sót là có. Nếu xảy ra chắc chung ta biết hậu quả với mình như thế nào.
....
Trong cuộc sống, từ việc làm phần nào thể hiện tính người và chính đây cũng sẽ làm ảnh hưởng tới cuộc sống của họ rất nhiều. Tôi nhận thấy, sự cẩn thận là cần cho bất cứ ai đó, người càng giữ trọng trách lớn thì càng phải cẩn thận nhiều từ lời nói đến hành động, rất có thể một sự sai lầm sẽ dấn đến một bước ngoặt của họ. Với việc lập trình, ứng dụng càng lớn thì càng phải cẩn thận từng dòng code.

Có những điều khi chưa xảy ra thì chưa nói. Với tôi, mọi thứ đã xảy ra nên tôi đang phải học nhiều về tính cẩn thận.
 
He he, Great! Đây là điều lúc nào tôi cũng nói với người lập trình.

Nhưng mà TuanVNUNI cẩn thận quá khi viết:

Dim obj As Object, Object2 As Object
Set obj = Nothing

1. Vừa mới khai báo
Dim obj As Object xong, chưa creat nó thì ko nhất thiết tự nhiên phải Set obj = Nothing. Chưa hết, thông thường người ta thường Check 1 cái gì đó trước khi thực hiện 1 câu lệnh.

Ví dụ: If Not objCustomer Is Nothing Then Set objCustomer = Nothing.
Tương tự cho việc kill file, close recordset, disconnect dbconnection, v.v...

2. obj là tiếp đầu ngữ, khai báo như vậy ko đúng Namming Convention lắm vì thiếu phần Tên. Ví dụ: objCustomer, objEmployee

3.
Object2 chưa được khai báo đúng theo Namming Convention. Vẫn còn tiếp tục cần tính cẩn thận hơn.

4. Có 1 nhầm lẫn là khi viết trong VB tạo nên tính cẩu thả. Như vậy không đúng cho lắm. Thực sự, ở bất cứ môi trường nào, công việc nào cũng đều cần cẩn thận. Viết VB hay Excel mà cẩu thả thì lỗi xảy ra rất... không thể chấp nhận được với những ứng dụng mang tính thương mại cao. Trong VB, khi sử dụng các Windows Resources mà ko biết viết thì cũng treo máy, hết resources như viết trong Visual C thôi. Có rất nhiều ví dụ cho thấy, chạy 100 TRIỆU BẢN GHI thì ko bao giờ chạy nổi. Nhưng có những ví dụ cho thấy máy cấu hình bình thường vẫn có khả năng sử lý với dữ liệu không lồ nói trên. Nhiều khi công nghệ nó quyết định vấn đề này 1 chút nhưng rõ ràng tất cả những cái đó có thể viết trên Delphi, VC hay VB hay những ngôn ngữ khác trên .NET.


5.
Nhưng các chuyên gia nói đó là các ngôn ngữ cho chuyên gia còn VB chưa phải nơi thể hiện đẳng cấp thực sự (về mặt ngôn ngữ).
Vấn đề này thì Tuân lại hoàn toàn nhầm rồi. Cho dù HĐH Windows có được viết = C đi chăng nữa cũng ko ai nói thế cả - rất hạn chế khi đưa ra nhưng tuyên bố như vậy vì mình ko phải là những Micro$oft Experts. Trên rất nhiều diễn đàn đã có những tranh luận tương tự như vậy rồi nhưng ko đi đến đâu cả (ai cũng biết là VC thì gần gũi hơn với ASM và ngôn ngữ máy hơn VB rồi, và khi họ viết sang VB, họ viết y chang như cách viết trên VC) và bây giờ cũng chả còn mấy người trên những diễn đàn đó còn viết những ngôn ngữ đó nữa. Họ chuyển hết lên Visual Studio .NET rồi. Trong khi mình đang tranh luận (dù ko tranh luận thì cũng ko nên nói thế) VB với C++ thì M$ đưa ra Visual Studio 2008 Beta cho Windows Vista, .NET Framework 3.5 Beta rồi. Thôi, ko nói chuyện này nữa vì có thể gây ra đau đầu.

Những đoạn ASM code như thế này cũng được chuyển đổi sang VB một cách hoàn hảo không khác gì viết trong VC.

Mã:
....
;************
;Data storage
    dd_nCallStack        dd 0        ;WndProc call stack counter
    dd_bBypassing        dd 0        ;Shutdown flag
    dd_lWnd            dd 0        ;Window handle
    dd_fnEbMode         dd 0        ;EbMode function address
    dd_fnCallWinProc    dd 0        ;CallWindowProc function address
    dd_fnSetWinLong    dd 0        ;SetWindowsLong function address
    dd_fnVirtualFree    dd 0        ;VirtualFree function address
    dd_fnIsBadCodePtr    dd 0        ;ISBadCodePtr function address
    dd_objOwner         dd 0        ;Owner object address
    dd_addrWndProc    dd 0        ;Original WndProc address
    dd_addrCallback    dd 0        ;Callback address
    dd_addrTableB        dd 0        ;Address of before original WndProc message table
    dd_addrTableA        dd 0        ;Address of after original WndProc message table
    dd_lParamUser        dd 0        ;User defined callback parameter
    dd_fnDestroyWindow  dd 0        ;DestroyWindow function address
    dd_SetTimer        dd 0        ;SetTimer function address
    dd_KillTimer        dd 0      ;KillTimer function address
    dd_timerID        dd 0        ;TimerID returned by SetTimer

;***********
;Thunk start    
Align 4
    xor     eax, eax                ;Zero eax, lReturn in the ebp stack frame
    xor     edx, edx                ;Zero edx, bHandled in the ebp stack frame
    pushad                        ;Push all the cpu registers on to the stack
    mov     ebp, esp                ;Setup the ebp stack frame
    mov     ebx, 012345678h            ;dummy Address of the data, patched from VB

    xor     esi, esi                ;Zero esi

    inc     dword nCallStack            ;Increment the WndProc call counter

    cmp    bBypassing, esi        ;bypassing if not zero
    jnz    _bypass
                        ;Check for dead callback address
    push    dword addrCallback    ;Push the callback address
    call    near fnIsBadCodePtr     ;Check the code is live
    jnz    _set_bypass            ;if non-zero, set bBypassing flag & bypass

    cmp     fnEbMode, eax            ;Check if the EbMode address is set
    jz     _before            ;if zero, user not in IDE

    call    near fnEbMode            ;Determine the IDE state
    cmp     eax, dword 1            ;If EbMode = 1
    je    _before                ;Running normally
    
    test    eax, eax                ;If EbMode = 0 then shut down
    jnz    _bypass            ;else on breakpoint

Align 4
_set_bypass:
    mov    bBypassing, dword M_RELEASE    ;set bypass flag indicating result of END

Align 4
_bypass:
    call    _wndproc                ;EbMode = 2, breakpoint... call original WndProc
    jmp     _return                ;Return

....
Đoạn viết bằng VB ... dài hơn nên ko post lên đây được.

Hy vọng Tuân vẫn có tiếp tục những chủ đề như thế này (và hy vọng hơn cả là có nhiều người học được từ những chủ đề như thế này)

Hic, lại viết tiếp rồi.
 
Lần chỉnh sửa cuối:
Em biện hộ một chút
Dim obj As Object ->Ý nói là khai báo một đối tượng nào đó
Set obj = Nothing ->Ý nói sau n bước nào đó và giải phóng đối tượng, sau đó lại dùng hàm VBA_SUM(obj) để tính đối tượng đó, cố tình tạo tình huống thôi.
Dù sao ví dụ test chưa rõ ràng lắm.
Nhưng các chuyên gia nói đó là các ngôn ngữ cho chuyên gia còn VB chưa phải nơi thể hiện đẳng cấp thực sự (về mặt ngôn ngữ).
Em cũng chỉ dám nói nười khác là "các chuyên gia" đã đánh giá chứ bản thân chưa khẳng định hoàn toàn. Em cũng đã nhận thấy có một số vấn đề mà VB không làm được?
Như ở đây
http://www.giaiphapexcel.com/forum/newthread.php?do=newthread&f=31
Nếu làm được anh có thể hướng dẫn giúp.

Em nghĩ đây là chủ đề cũng rất cần mọi người chia sẻ để cùng học hỏi thêm. Với kinh nghiệm của mình, em mong anh tiếp tục chia sẻ cùng mọi người.
 
Em cũng đã nhận thấy có một số vấn đề mà VB không làm được?
Như ở đây
http://www.giaiphapexcel.com/forum/n...newthread&f=31
Nếu làm được anh có thể hướng dẫn giúp.
Re 1: Cái link mà Tuân đưa hình như là New 1 topic :)

Nếu là vấn đề Unicode ở MDI caption ko phải là ở vấn đề không lên được tiếng Việt Unicode ở Windows Classic Theme hay Win2k vì ở 2 môi trường này thì cách hiển thị sẽ là khác mà ko dùng theo cách bình thường mà mình vẫn hay làm. Vấn đề khó là vấn đề làm thế nào để quản lý cái vụ khi thì child form maximized và un-maximized cơ. Còn chuyện để nó lên Unicode cho 2 môi trường đó, chỉ cần tìm hiểu 1 chút về vấn đề Unicode trên WinXP Theme, Win2k, Win98 là OK thôi mà. Nhưng vì tớ chưa đặt vấn đề đó lên cao nên chưa làm thôi.

Có 1 trang web nói rất kỹ về vấn đề Unicode này rồi mà.

As you can see from the above Screenshot XP Themes give a nice appearance to controls but they may have undesirable side effects when it comes to Unicode.​
  • If you manage to coerce a Vb ANSI control to display Unicode, you will more than likely lose the Unicode(displays as ???) when a Theme is applied. One exception to this is using a hook and calling API "SetWindowTextW" when you receive the HCBT_ACTIVATE message. This appears to work both with and without a XP Theme.​
  • You can circumvent this behaviour by creating from scratch an API Owner-Draw control where you apply the Theme manually and not via a Manifest file. API 'DrawThemeText' can be called just like DrawTextW where you supply a RECT, Pointer to a Unicode string, and an alignment Flag.​
  • You may also want to fix some of the anomalies(aka bugs) with XP Themes or even add new features. For example you can see from the above screenshot that the button corners are transparent, something you don't get using a Manifest. Using a Manifest a background rectangle is painted(API DrawThemeParentBackground).​
  • If no Theme is available or activated you can drop back to a standard or flat style control and use DrawTextW to output the Unicode text.​

À, vừa rồi thấy link Tuân hỏi ko phải là vấn đề Unicode ở MDI Form. :)
 
Lần chỉnh sửa cuối:
Các bác TuanVNUNIbác smbsolutions quả là cao siêu !
Bữa nào vào Sài Gòn hú em một tiếng - để hân hoan đón tiếp các bác !
 
lời lẽ của những bậc cao thủ có khác,em sẽ cố gắng học hỏi 1 ngày để bàn luận như thế này trên diễn đàn này. Chắc chắn rồi
 
vungoc đã viết:
Các bác TuanVNUNIbác smbsolutions quả là cao siêu !
Bữa nào vào Sài Gòn hú em một tiếng - để hân hoan đón tiếp các bác !

"Nại" bon chen rồi.

Định " cướp trên giàn mướp " à ??

Đây mua nhà ở đầu cầu Sài Gòn từ lâu chỉ để đón các bác đó không đấy.

Từ An Sương chạy lên kịp không ???
 
vungoc đã viết:
Các bác TuanVNUNI và bác smbsolutions quả là cao siêu !
Bữa nào vào Sài Gòn hú em một tiếng - để hân hoan đón tiếp các bác !

Mr Okebab đã viết:
"Nại" bon chen rồi.

Định " cướp trên giàn mướp " à ??

Đây mua nhà ở đầu cầu Sài Gòn từ lâu chỉ để đón các bác đó không đấy.

Từ An Sương chạy lên kịp không ???

Hi, cảm ơn các anh em nha! Hy vọng một ngày nào đó không xa được nhậu một bữa thỏa thích với mọi người.

(Nói nhỏ nhé! Mọi người cũng siêu lắm, mỗi người có lĩnh vực riêng, hơn nhau ở cái biết khoe mà--=0 )
 
Anh TuanVNUNI oi!
Anh giúp em viết hàm sumif bằng VBA nhé
Cám ơn anh
 
VBA_SumIf

Hôm nay xin trình bày với các bạn cách viết hàm có điều kiện cho Excel trong VBA. Hàm VBA_SUMIF
Mã:
[COLOR="Lime"]'Phien ban nay chua chay dung neu criteria su dung ? hoac *[/COLOR]
Function VBA_SUMIF(ByVal test_range, ByVal criteria, Optional ByVal sum_range) As Variant
Dim OperArg 'TO get Array
Dim VT As VbVarType, VT_Test As VbVarType
Dim strOper, StrL2 As String, nOper As Long
Dim nLow As Long, nHigh As Long, i As Long, nLow_Sum As Long, nHigh_Sum As Long
Dim HasSumRange As Boolean
Dim nSUM

VT_Test = VarType(test_range)
If VT_Test <> vbArray And VT_Test <> vbObject And VT_Test <> 8204 Then
    'Raise Error
    GoTo RaiseErr
End If

VT_sum = VarType(sum_range)
HasSumRange = (VT_sum <> vbError)
If HasSumRange And VT_sum <> vbArray And VT_sum <> vbObject And VT_sum <> 8204 Then
    'Raise Error
    GoTo RaiseErr
End If

VT = VarType(criteria)

If VT <> vbString And VT <> vbObject Then
    'Raise Error
    GoTo RaiseErr
End If

If VT = vbObject Then
    If criteria Is Nothing Then
        'Raise Error
        GoTo RaiseErr
    End If
End If


OperArg = Array("<>", ">=", "<=", "=", ">", "<", "")
criteria = Trim(criteria)
StrL2 = Left(criteria, 2)
For Each strOper In OperArg
    If InStr(StrL2, strOper) > 0 Then
        Exit For
    End If
Next

nOper = Len(strOper)
criteria = Mid(criteria, nOper + 1, Len(criteria) - nOper)

If strOper = "" Then strOper = "="

On Error GoTo GetcDim
    'For Excel.Range
    nLow = 1
    nHigh = test_range.Rows.Count
    GoTo Begin

GetcDim:
    'For Array
If VT_Test = 8204 Or VT_Test = vbArray Then
    nLow = LBound(test_range, 1)
    nHigh = UBound(test_range, 1)
End If

Begin:
On Error GoTo RaiseErr:
VBA_SUMIF = 0

For i = nLow To nHigh
    If HasSumRange Then
        VT = VarType(sum_range(i))
    Else
        VT = VarType(test_range(i))
    End If
    
    If VT = vbEmpty Or VT = vbError Or VT = vbString Or VT = vbNull Then
        GoTo EndFor
    End If
    
    If IsTrue(strOper, test_range(i), criteria) Then
        If HasSumRange Then
            VBA_SUMIF = VBA_SUMIF + sum_range(i)
        Else
            VBA_SUMIF = VBA_SUMIF + test_range(i)
        End If
    End If
    
EndFor:
Next i

EndFunc:
    Exit Function
    
RaiseErr:
    VBA_SUMIF = "#N/A"
End Function

Function IsTrue(ByVal strOper As String, ByVal Value1, ByVal Value2) As Boolean
    
    If IsNumeric(Value2) Then Value2 = Val(Value2)

    IsTrue = False
    If strOper = "=" Then
        IsTrue = (Value1 = Value2)
    ElseIf strOper = "<>" Then
        IsTrue = (Value1 <> Value2)
    ElseIf strOper = ">=" Then
        IsTrue = (Value1 >= Value2)
    ElseIf strOper = "<=" Then
        IsTrue = (Value1 <= Value2)
    ElseIf strOper = "<" Then
        IsTrue = (Value1 < Value2)
    ElseIf strOper = ">" Then
        IsTrue = (Value1 > Value2)
    End If

End Function
Các thủ tục để test trong VBA.

Mã:
Sub Test_VBA_SUMIF_Array()
Dim ValueArg

ValueArg = Array(200, 300, 400)
MsgBox VBA_SUMIF(ValueArg, ">200")

End Sub

Sub Test_VBA_SUMIF_Range()
Dim ValueArg As range

Set ValueArg = range("DOANHTHU") [COLOR="Lime"]'Sheets("Sheet2").Range("F7:F9")[/COLOR]
MsgBox VBA_SUMIF(ValueArg, ">200")

Set ValueArg = Nothing

End Sub

Chữa một chút các bạn sẽ có hàm VBA_COUNTIF. Nâng cao hơn nữa chúng ta có thể viết các hàm SumIfs, CountIfs như trong Excel2007.

Chúc các bạn ngày cuối tuần vui vẻ!
 

File đính kèm

  • Ky thuat viet ham cho Excel trong VBA.xls
    69 KB · Đọc: 48
Lần chỉnh sửa cuối:
Web KT
Back
Top Bottom