Thảo luận mở rộng về "Useful functions - Các hàm hữu ích" của Lê Văn Duyệt (2 người xem)

Liên hệ QC

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

ndu96081631

Huyền thoại GPE
Thành viên BQT
Super Moderator
Tham gia
5/6/08
Bài viết
30,703
Được thích
53,963
Mình vào topic này:
Useful functions - Các hàm hữu ích
Của tác giả Levanduyet
Quả đúng hầu hết các hàm đều rất hửu ích. Tuy nhiên mình cảm thấy cần phải cải tiến lại rất nhiều ---> Gần 80% các hàm ấy mình nghĩ có thể rút gọn lại, thậm chí có hàm mình rút gọn lại chỉ còn đúng 1 dòng, ví dụ
PHP:
Function FileExists(fname) As Boolean
  FileExists = Dir(fname) <> ""
End Function
hoặc
PHP:
Function InRange(rng1, rng2) As Boolean
  On Error Resume Next
  InRange = Union(rng1, rng2).Address = rng2.Address
End Function
Đây chỉ là nhận định cá nhân (chưa chắc chính xác) nhưng cũng rất hào hứng, định vào viết bài trao đổi nhưng đáng tiếc topic ấy đã bị khóa
Mình nghĩ dạng bài này nên để ở dạng "mở" vì nhiều ý kiến khác nhau biết đâu sẽ tìm ra được giải pháp tốt hơn
(Chỉ với tấm lòng muốn học hỏi, nếu có gì không phải xin các bạn bỏ qua cho)
 
Khai báo các đối số cũng như sử dụng kiểu giá trị trong hàm

Việc mở mục "Trao đổi góp ý" như thế này là cần thiết để mọi người cùng trao đổi đưa ra giải pháp tốt nhất. Vừa mang tính hoạc thuật vừa mang tính thực dụng.

Em cũng xin góp ý nhỏ về việc khai báo các đối số cũng như sử dụng kiểu giá trị trong hàm.

Mã:
Function FileExists(fname) As Boolean
  FileExists = Dir(fname) <> ""
End Function

Nên khai báo như sau:
Mã:
Function FileExists([COLOR="Blue"]Byval fname As String[/COLOR]) As Boolean
  FileExists = Dir(fname) <> ""
End Function

Trong VB, nếu khai báo biến tham số (fname) như thế này
Function FileExists(fname) As Boolean

VB sẽ hiểu là (tương đương với khai báo):
Function FileExists(Byref fname As Variant) As Boolean

+ Vấn đề ByVal, ByRef:
Với khai báo Byref , biến tham số sẽ nhận giá trị khi thoát khỏi hàm, nó có thể nhận giá trị ngẫu nhiên trong vùng nhớ mà người dùng không kiểm soát được (không phải lúc nào cũng xảy ra). Sẽ rất nguy hiểm nếu chúng ta vô tình sử dụng ByRef với biến fname , và các dòng lệnh sau chúng ta sử dụng lại biến này, khi mà có thể giá trị của nó đã không còn nguyên như trước.

Như vậy, nếu biến tham số không có nhu cầu nhận giá trị trả về thì nên khai báo là ByVal

Ví dụ sau sẽ cần dùng đến ByRef

Mã:
Function GetListFiles(ByVal Path As String, ByRef ListFiles) As Long
    Dim FileName As String
    GetListFiles = 0
    FileName = Dir(Path & "\*.*", vbArchive) 'FindFirst
    While FileName <> "." And FileName <> ".." And FileName <> "" 'Test file is valid
        GetListFiles = GetListFiles + 1 'Count list
        ReDim Preserve ListFiles(GetListFiles) As String 'Increasing array
        ListFiles(GetListFiles) = FileName
        FileName = Dir 'FindNext file
    Wend
    
End Function
'-----------------------------------------------------------------
Sub Test()
    Dim ListFiles() As String
    Dim FileCount As Long, I As Long
    
    FileCount = GetListFiles("D:", ListFiles())
    If FileCount > 0 Then
        For I = 1 To FileCount
            Cells(I, 1).Value = ListFiles(I) 'Write the file name to sheet, from row 1 to FileCount
        Next I
    End If
    
End Sub

Chúng ta có thể khai báo hàm tương đương như sau:
Function GetListFiles(ByVal Path As String, ListFiles) As Long 'Không có ByRef

Nếu khai báo là
Function GetListFiles(ByVal Path As String, Byval ListFiles) As Long 'Hàm sẽ chạy lỗi, bởi ListFiles không nhận được giá trị.

Qua ví dụ về hàm GetListFiles() chúng ta thấy khi viết hàm cần sử dụng khai báo thật chuẩn: khi nào thì dùng ByVal, ByRef
Đọc qua hàm người ta phải thấy được mục đích của hàm, mục đích được thể hiện từ tên biến, tên hàm, khai báo. Cố gắng dùng đúng, dùng đủ.
Khi viết một hàm, cố gắng nhận tối đa thông tin từ nó. Thông tin được trả về từ tham số, từ hàm.

+ Vấn đề kiểu giá trị Variant
Nếu khai báo biến tham số (fname) như thế này
Function FileExists(fname) As Boolean có nghĩa là VB đã gán kiểu biến tham số là Variant. Kiểu giá trị này giúp cho người dùng đơn giản hoá vấn đề, dùng cho các trường hợp với các giá trị khác kiểu. Tuy nhiên nếu người dùng thực sự không có mục đích khai thác biến kiểu Variant mà lại dùng nó thì rất lãng phí. Ứng dụng của bạn sẽ phải sử dụng một khối hới lớn-->Tốc độ chạy chậm-->lãng phí năng lực của chip.
Các hàm trong VB, VBA các biến tham số phần lớn dùng kiểu Variant vì họ có chủ đích. Ví dụ hàm SUM, ta có thể dùng
=SUM(1,2)
=SUM(A1, A2)
=SUM(A1:A10)
Nhìn vào đối số thứ nhất, khi thì là một số, khi thì là một ô, khi thì là một vùng, bản chất, biến tham số thứ nhất là kiểu Variant.

Khai thác biến Variant chúng ta sẽ bàn sâu ở topic khác.

Trong bài viết này, tôi chỉ muốn nhấn mạnh, chúng ta không dùng Variant khi chúng ta biết chắc chỉ dùng kiểu giá trị String hay giá trị cụ thể nào đó.

Tóm lại, thay vì dùng khai báo hàm
Function FileExists(fname) As Boolean

Chúng ta nên dùng
Function FileExists(ByVal fname As String) As Boolean
 
Lần chỉnh sửa cuối:
Cảm ơn TuanVNUNI về các kiến thức byRef, byVal mà bấy lâu nay tôi vẫn còn nhiều lúng túng
Nói như vậy thì hàm:
PHP:
Function InRange(rng1, rng2) As Boolean
...
...
End Function
Có phải nên ghi thế này:
PHP:
Function InRange(byVal rng1 As Range, byVal rng2 As Range) As Boolean
...
...
End Function
hay không?
Tôi thường thấy người ta ghi thế này:
PHP:
 Function InRange(rng1 As Range, rng2 As Range) As Boolean
 ...
 ...
 End Function
Xin hỏi: Có gì không ổn? rng1rng2 đã chỉ định chính xác thuộc biến RANGE rồi còn gì?
 
Lần chỉnh sửa cuối:
Thảo luận về 1 số hàm khác:
----------------------------------------------------------------------------------------------------
1> Hàm FileNameOnly
Tôi nghĩ chỉ cần dùng InSrtRev là đủ, không cần dùng đến vòng lập chi cho mất công
----------------------------------------------------------------------------------------------------
2> Hàm RangeNameExists
Có thể kiểm tra bằng cách tìm INDEX của nó. Ví dụ:
PHP:
Function RangeNameExists(nname As String) As Boolean
  On Error Resume Next
  RangeNameExists = ActiveWorkbook.Names(nname).Index
End Function
Vậy cũng không cần dùng đến vòng lập luôn
----------------------------------------------------------------------------------------------------
3> Hàm WorkbookIsOpen
Duyệt viết hàm như sau:
PHP:
Private Function WorkbookIsOpen(wbname) As Boolean
'   Returns TRUE if the workbook is open
    Dim x As Workbook
    On Error Resume Next
    Set x = Workbooks(wbname)
    If Err = 0 Then WorkbookIsOpen = True _
        Else WorkbookIsOpen = False
End Function
Tin chắc rằng hàm này chẳng bao giờ làm việc chính xác nếu như Workbook cần kiểm tra đang khởi động trên 1 session khác ---> Vụ này cả thế giới họ cũng đăng thắc mắc mà chưa tìm thấy câu trả lời
Không biết TuanVNUNI có ý kiến gì không? Nếu dùng API liệu có thể giải quyết được vấn đề không nhỉ?
-------------------------
Nói thêm: Khi Workbook được khởi động trên 1 session khác thì trong cửa sổ TaskManager ta sẽ nhìn thấy cùng 1 lúc 2 tiến trình Excel.exe (thay vì chỉ có một)
 
To: All,

Rất cám ơn mọi người về việc góp ý.
Như topic tôi có giải thích tại sao tôi đưa ra Thư viện mã lập trình và mọi người cũng đã đồng ý.

phantuhuong đã viết:
Nếu không nhầm thì phần lớn các hàm đó đều là do bác Duyệt sưu tầm bác Tuấn ạ.
Cái này xin trả lời là sưu tầm và tổng hợp (tuỳ từng trường hợp)

Tôi muốn các đoạn mã đưa vào Thư viện mã lập trình sẽ dễ hiểu đối với người áp dụng. Chính vì vậy tôi sẽ vẫn giữ nguyên bản và sẽ đưa thêm các góp ý rút ngắn của các bạn. (Rút gọn như Anh Tuấn tôi đã biết nhưng cần phải chỉnh một tí).

Xin các bạn tiếp tục đóng góp ý kiến tại đây.
Xin cám ơn.

Lê Văn Duyệt
 
Lần chỉnh sửa cuối:
Theo tôi, nói một cách đơn giản thì như thế này:
Khai báo biến kiểu ByVal (By Value) có nghĩa là các thay đổi của biến này chỉ có giá trị sử dụng trong riêng một thủ tục. Giá trị ban đầu của biến vẫn được lưu lại, kết thúc thủ tục biến sẽ trả về giá trị ban đầu.

Khai báo biến kiểu ByRef (By Reference) thì ngược lại. Khi kết thúc thủ tục, nếu giá trị của biến bị thay đổi thì biến sẽ nhận giá trị mới này.

Thử ví dụ sau bạn sẽ thấy sự khác nhau giữa ByVal và ByRef.
Tạo hai thủ tục như sau:
PHP:
Sub Test()
Dim MyNumber As Integer
MyNumber = 10
Call IncreaseMyNumber(MyNumber)
MsgBox (MyNumber)
End Sub
PHP:
Private Sub IncreaseMyNumber(ByRef MyNumber As Integer)
MyNumber = MyNumber + 1
End Sub
Chạy thủ tục Test(), kết quả hiển thị trên MsgBox là 11
Thay đổi cách khai báo biến MyNumber trong thủ tục IncreaseMyNumber từ ByRef thành ByVal
PHP:
Private Sub IncreaseMyNumber(ByVal MyNumber As Integer)
MyNumber = MyNumber + 1
End Sub
Chạy lại thủ tục Test(), kết quả hiển thị trên MsgBox là 10

Khai báo ByRef khi kết thúc thủ tục biến MyNumber sẽ nhận giá trị mới (MyNumber + 1 = 11)
Khai báo ByVal khi kết thúc thủ tục biến MyNumber sẽ nhận giá trị cũ (MyNumber = 10)
 
Cũng tranh thử SPAM vài câu.

Mình thì điều đầu tiên viết code là tường minh.

Rất không ưa các hàm người dùng hay macro mà không có Option Explicit
Nhiều lúc thấy ngại & bỏ qua luôn.
(Kiểu Dim i As Long cũng dị ứng với mình luôn.. . .Khà, khà. . )

Thật tình cũng chưa rành rẽ lắm vê ByVal hay ByRef nên hiếm xài. Chỉ xài khi nghiền ngẫm kỹ là cần xài nó.

Mình hay xài lại biến trong 1 thủ tục thôi. Không mấy khi xài biến từ thủ tục này chuyển sang thủ tục khác.

(Giống như không dám đụng đến con dao hai lưỡi chút nào) . Mình cảm giác rằng xài thứ này còn tốn nhiều công sức để bẫy lỗi, điều kiện này nọ; Nên khai báo quách 1 biến mới xài trong thử tục đó cho rồi.

Quan trọng với mình là, vài năm sau cần đọc lại thì vẫn hiểu nhanh gần như lúc thoát thai ra nó lúc này.

Tiết kiệm thời gian với mình là ưu tiên hàng đầu; Còn mấy năm sống nữa đâu, các bạn!
 
Mình vào topic này:
Useful functions - Các hàm hữu ích
Của tác giả Levanduyet
Quả đúng hầu hết các hàm đều rất hửu ích. Tuy nhiên mình cảm thấy cần phải cải tiến lại rất nhiều ---> Gần 80% các hàm ấy mình nghĩ có thể rút gọn lại, thậm chí có hàm mình rút gọn lại chỉ còn đúng 1 dòng, ví dụ
PHP:
Function FileExists(fname) As Boolean
  FileExists = Dir(fname) <> ""
End Function

Anh ơi, nếu fname = "" thì FileExists trả về TRUE.

Lê Văn Duyệt
 
Lần chỉnh sửa cuối:
Có lẽ ta mở thêm topic tối ưu code và đưa ra 1 loạt bài toán của mình hoặc sưu tầm trên mạng để mọi người cùng thảo luận thì hợp lý hơn.
 
Cảm ơn TuanVNUNI về các kiến thức byRef, byVal mà bấy lâu nay tôi vẫn còn nhiều lúng túng
Nói như vậy thì hàm:
PHP:
Function InRange(rng1, rng2) As Boolean
...
...
End Function
Có phải nên ghi thế này:
PHP:
Function InRange(byVal rng1 As Range, byVal rng2 As Range) As Boolean
...
...
End Function
hay không?
Tôi thường thấy người ta ghi thế này:
PHP:
 Function InRange(rng1 As Range, rng2 As Range) As Boolean
 ...
 ...
 End Function
Xin hỏi: Có gì không ổn? rng1rng2 đã chỉ định chính xác thuộc biến RANGE rồi còn gì?


Vâng, có thể thay
Function InRange(rng1, rng2) As Boolean

thành
Function InRange(byVal rng1 As Range, byVal rng2 As Range) As Boolean
hoặc
Function InRange(rng1 As Range, rng2 As Range) As Boolean

Hai bến rng1, rng2 đều là biến kiểu đối tượng Object/Range nên dùng ByVal hay ByRef là không khác nhau. Biến kiểu Object là làm việc theo tham chiếu nên khi khai báo người ta hay dùng ByRef như thế này
Function InRange(rng1 As Range, rng2 As Range) As Boolean
 
Có lẽ ta mở thêm topic tối ưu code và đưa ra 1 loạt bài toán của mình hoặc sưu tầm trên mạng để mọi người cùng thảo luận thì hợp lý hơn.
Thầy ơi! Vậy mình sửa tiêu đề của Topic này cho nó mang tính tổng quát hơn có được không?
 
Thảo luận về 1 số hàm khác:
----------------------------------------------------------------------------------------------------
1> Hàm FileNameOnly
Tôi nghĩ chỉ cần dùng InSrtRev là đủ, không cần dùng đến vòng lập chi cho mất công
----------------------------------------------------------------------------------------------------
2> Hàm RangeNameExists
Có thể kiểm tra bằng cách tìm INDEX của nó. Ví dụ:
PHP:
Function RangeNameExists(nname As String) As Boolean
  On Error Resume Next
  RangeNameExists = ActiveWorkbook.Names(nname).Index
End Function
Vậy cũng không cần dùng đến vòng lập luôn
----------------------------------------------------------------------------------------------------
3> Hàm WorkbookIsOpen
Duyệt viết hàm như sau:
PHP:
Private Function WorkbookIsOpen(wbname) As Boolean
'   Returns TRUE if the workbook is open
    Dim x As Workbook
    On Error Resume Next
    Set x = Workbooks(wbname)
    If Err = 0 Then WorkbookIsOpen = True _
        Else WorkbookIsOpen = False
End Function
Tin chắc rằng hàm này chẳng bao giờ làm việc chính xác nếu như Workbook cần kiểm tra đang khởi động trên 1 session khác ---> Vụ này cả thế giới họ cũng đăng thắc mắc mà chưa tìm thấy câu trả lời
Không biết TuanVNUNI có ý kiến gì không? Nếu dùng API liệu có thể giải quyết được vấn đề không nhỉ?
-------------------------
Nói thêm: Khi Workbook được khởi động trên 1 session khác thì trong cửa sổ TaskManager ta sẽ nhìn thấy cùng 1 lúc 2 tiến trình Excel.exe (thay vì chỉ có một)

Đúng là vấn đề kiểm tra một file đang mở rất khó khi nó đã được mở ở nhiều session. Em cũng nghĩ tới phải giải quyết bởi các hàm API nhưng vẫn chưa tìm ra cách nào.
 
Đúng là vấn đề kiểm tra một file đang mở rất khó khi nó đã được mở ở nhiều session. Em cũng nghĩ tới phải giải quyết bởi các hàm API nhưng vẫn chưa tìm ra cách nào.
Liệu có thể dùng API để tìm tên của các cửa sổ đang mở hay không? ---> Lấy tiêu đề cửa sổ chẳng hạn (chỉ ác cái là lở người ta dùng code đổi tiêu đề thì chắc "tèo" luôn quá)
 
Theo tôi thật sự ra việc kiểm tra các cửa sổ đang mở, dĩ nhiên là mình làm được rồi bằng các hàm API.
Còn như Anh Tuấn nói chỉ ác cái là lở người ta dùng code đổi tiêu đề thì chắc "tèo" luôn quá ~~> Thì ai mà làm dzậy.

Lê Văn Duyệt
 
Liệu có thể dùng API để tìm tên của các cửa sổ đang mở hay không? ---> Lấy tiêu đề cửa sổ chẳng hạn (chỉ ác cái là lở người ta dùng code đổi tiêu đề thì chắc "tèo" luôn quá)

Dùng API để lấy tiêu đề của các cửa sổ đang mở là được nhưng phương pháp này không chuẩn. Em nghĩ tới giải pháp, nhận từng process "excel.exe" (làm được) rồi nhận biến đối tượng Application (có vẻ rất khó?), từ biến này ta kiểm tra workbook đang mở hay không?
 
Có lý! Vậy thêm cái IF nữa ha:
If fname <> "" Then FileExists = Dir(fname) <> ""
Em cũng tranh thủ vài hàm góp nhặt được, hi hi
PHP:
‘ Kiểm tra sự tồn tại của 1 File
Function bFileExists(rsFullPath As String) As Boolean 
  bFileExists = CBool(Len(Dir$(rsFullPath)) > 0)
End Function

 -------------------------------------------------------
‘ Kiểm tra 1 WorkBook có đang mở hay không ?
Function bWorkbookIsOpen(rsWbkName As String) As Boolean 
On Error Resume Next   
bWorkbookIsOpen = CBool(Len(Workbooks(rsWbkName).Name) > 0) 
End Function 

 --------------------------------------------------------
‘ Kiểm tra sự tồn tại của 1 Sheet
Function WksExists(wksName As String) As Boolean 
   On Error Resume Next    
WksExists = CBool(Len(Worksheets(wksName).Name) > 0)
End Function
TDN
 
Lần chỉnh sửa cuối:
Em cũng tranh thủ vài hàm góp nhặt được, hi hi
PHP:
‘ Kiểm tra sự tồn tại của 1 File
Function bFileExists(rsFullPath As String) As Boolean 
  bFileExists = CBool(Len(Dir$(rsFullPath)) > 0)
End Function

 -------------------------------------------------------
‘ Kiểm tra 1 WorkBook có đang mở hay không ?
Function bWorkbookIsOpen(rsWbkName As String) As Boolean 
On Error Resume Next   
bWorkbookIsOpen = CBool(Len(Workbooks(rsWbkName).Name) > 0) 
End Function 

 --------------------------------------------------------
‘ Kiểm tra sự tồn tại của 1 Sheet
Function WksExists(wksName As String) As Boolean 
   On Error Resume Next    
WksExists = CBool(Len(Worksheets(wksName).Name) > 0)
End Function
TDN

Hi hi hi, cái này anh đã đưa lên rồi.

Lê Văn Duyệt
PS: Chán tedaynui quá, hỏng thèm đọc luôn.
 
----------------------------------------------------------------------------------------------------
3> Hàm WorkbookIsOpen
Duyệt viết hàm như sau:
PHP:
Private Function WorkbookIsOpen(wbname) As Boolean
'   Returns TRUE if the workbook is open
    Dim x As Workbook
    On Error Resume Next
    Set x = Workbooks(wbname)
    If Err = 0 Then WorkbookIsOpen = True _
        Else WorkbookIsOpen = False
End Function
Tin chắc rằng hàm này chẳng bao giờ làm việc chính xác nếu như Workbook cần kiểm tra đang khởi động trên 1 session khác ---> Vụ này cả thế giới họ cũng đăng thắc mắc mà chưa tìm thấy câu trả lời (~~> hi hi hi, anh chưa tìm hiểu thôi...)
Không biết TuanVNUNI có ý kiến gì không? Nếu dùng API liệu có thể giải quyết được vấn đề không nhỉ?
-------------------------
Nói thêm: Khi Workbook được khởi động trên 1 session khác thì trong cửa sổ TaskManager ta sẽ nhìn thấy cùng 1 lúc 2 tiến trình Excel.exe (thay vì chỉ có một)

Anh ơi,

Em đã giới thiệu rồi (nhưng tại vì em chưa kiểm tra theo ý anh nói thôi.) Anh xem ở đây nha.

Chúc anh nghỉ lễ vui vẻ nha.

Lê Văn Duyệt
 
Anh ơi,

Em đã giới thiệu rồi (nhưng tại vì em chưa kiểm tra theo ý anh nói thôi.) Anh xem ở đây nha.

Chúc anh nghỉ lễ vui vẻ nha.

Lê Văn Duyệt
Duyệt ơi!
Việc kiểm tra 1 file có đang mở hay không hoàn toàn khác với kiểm tra 1 workbook đang mở
Với 1 file, ta cần đường dẩn đầy đủ đến file, còn người dùng khi cần kiểm tra 1 Workbook có đang mở hay không, họ đâu quan tâm đến đường dẩn ---> Chỉ cần tên của Workbook có tồn tại trong cửa sổ Task Manager thì hàm phải trả về giá trị TRUE và ngược lại thì FALSE
Minh nghĩ 2 chuyện này hoàn toàn khác nhau ---> Nói chung là KHÓ (đã tìm khắp nơi nhưng chưa thấy code nào khả thi)
 
Web KT

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

Back
Top Bottom