Chuyên đề giải đáp những thắc mắc về code VBA

Liên hệ QC

maytinhvp01

Thành viên thường trực
Tham gia
27/7/13
Bài viết
390
Được thích
179
Mình muốn nhờ giải thich câu lệnh " If Ran.Cells(d, c) > max Then max = Ran.Cells(d, c) "
trong ví du:
Public Function LonNhat(Ran As Range)
Dim max As Double, v As Integer, d As Integer, c As Integer
max = Ran.Cells(1, 1)
For d = 1 To Ran.Rows.Count
For c = 1 To Ran.Columns.Count
If Ran.Cells(d, c) > max Then max = Ran.Cells(d, c)
Next c
Next d
v = Tim(max, Ran)
LonNhat = max
End Function
-------------------------------------------------------
[INFO1]Thông báo:
Vì topic này:
http://www.giaiphapexcel.com/forum/...ải-thích-các-code-đề-nghị-các-bạn-gửi-vào-đây
đã quá dài nên BQT đóng lại.
Nay tôi mở topic mới với cùng chủ đề: GIẢI THÍCH NHỮNG THẮC MẮC VỀ CODE
Các bạn nếu có nhu cầu giải thích code, vui lòng post tại đây nhé
NDU96081631

[/INFO1]
 
Chỉnh sửa lần cuối bởi điều hành viên:
Nói cho thật đúng ngôn ngữ mảng thì "phần tử đầu tiên" chả phải là 0 mà cũng chả phải là 1.
0 và 1 là chỉ số để truy phần tử mảng.
Nếu mảng bắt đầu từ 0 thì chỉ số phần tử đầu tiên là 0. Nếu bắt đầu từ 1 thì chỉ số phần tử đầu tiên là 1.
arr(i, j) là biểu thức truy ra phần tử ở chỉ số dòng i và chỉ số cột j của mảng arr.

Dim arr() là lệnh khai báo mảng động, chưa định trước số chiều và độ lớn (phạm vi chỉ số). Lưu ý rằng tôi nói mảng động. Mảng tĩnh nó khác.

ReDim arr(....) là lệnh xác định chiều và phạm vi chỉ số của mảng động. Khi sử dụng ReDim mà không xác định chỉ số dưới (trị của LBound) thì lệnh này mặc định chúng là 0. Mặc định này có thể đổi thành 1 nếu ngay đầu Module có lệnh dẫn trình dịch Option Base 1
Ở đầu mõi Module, trước khi có code khai báo biến và sub, function thì người code có thể nhét một số lệnh dẫn trình dịch. Và Option Base là một trong những lệnh dẫn này (Option Explicit là lệnh dẫn thứ hai)

arr = .Range("N8:AG10").Value là lệnh gán trị của Range vào mảng. Lệnh gán này gọi một hàm kín của đối tượng Range. Hàm này lấy trị Range và tự động xác định chiều cùng phạm vi chỉ số của mảng (có thể coi như nó gọi lệnh Redim), và mặc định chỉ số dưới, tức trị LBound là (1,1)

Chú: khi làm việc với mảng thì phải tìm hiểu cho rõ mỗi hàm của VBA mặc định chỉ số dưới ra sao.
Bác VetMini,
Về Option Base 1 thì con đã hiểu,còn vấn đề gán từ range vào mảng thì phần tử đầu tiên luôn mặc định là 1,đến ngày hôm nay con mới được biết. :D
Con cảm ơn các Bác đã giải thích và chỉ dẫn ạ.
 
Upvote 0
Dạ con chào Bác!
Hình như cái Help của con ở phiên bản office2016 nó hơi khác ạ :
https://docs.microsoft.com/en-us/of...nce/user-interface-help/array-function#syntax

Con sẽ tìm hiểu hiểu thêm ạ, cảm ơn Bác đã chỉ dẫn.
Có lẽ tôi đã viết rất rõ. Nhập Array là khi bạn chưa biết nhập chi tiết thế nào. Khi ra 1 danh sách thì như hình 1.
array1.JPG
Tôi đã nói rõ là trong danh sách đó thì click vào Using Arrays nhưng bạn lại click vào Array Function (mục đầu tiên trong hình 1).

Nếu bạn cuộn xuống dưới thì bạn sẽ thấy Using Arrays (hình 2) Click vào Declaring Arrays và đọc tiếp. Danh sách có nhiều mục chứ đâu có 1 mục mà bạn.
array2.JPG
 
Upvote 0
Có lẽ tôi đã viết rất rõ. Nhập Array là khi bạn chưa biết nhập chi tiết thế nào. Khi ra 1 danh sách thì như hình 1.
View attachment 216101
Tôi đã nói rõ là trong danh sách đó thì click vào Using Arrays nhưng bạn lại click vào Array Function (mục đầu tiên trong hình 1).

Nếu bạn cuộn xuống dưới thì bạn sẽ thấy Using Arrays (hình 2) Click vào Declaring Arrays và đọc tiếp. Danh sách có nhiều mục chứ đâu có 1 mục mà bạn.
View attachment 216102

Dạ, ý của con là, con đang sử dụng office 2016 nên có thể Help ở dạng Online hơi khác với Help Offline ở các phiên bản Office cũ hơn ạ.
Bác xem đoạn video con thao tác trên máy của con ạ.

Con cảm ơn Bác đã thông tin ạ.
 

File đính kèm

  • arr.zip
    4.9 MB · Đọc: 1
Upvote 0
Dạ, ý của con là, con đang sử dụng office 2016 nên có thể Help ở dạng Online hơi khác với Help Offline ở các phiên bản Office cũ hơn ạ.
Bác xem đoạn video con thao tác trên máy của con ạ.

Con cảm ơn Bác đã thông tin ạ.
Tôi không biết bạn cài Excel thế nào nhưng tôi nghĩ là cũng sẽ có help như của tôi. Không phải phiên bản này giống của tôi và phiên bản khác giống của bạn. Tùy thuộc vào lựa chọn thôi. Tôi không có 2016 nên chỉ đoán thế.

Khi tôi lần đầu tiên dùng help thì cũng của như bạn. Tức khi nhập vd. hichic vào thì có như hình 1. Nhìn thấy "Connected to Office.com"
Sau khi click vào đấy thì như hình 2 và tôi chuyển đánh dấu sang "Show content only from this computer" sau đó đóng cửa sổ. Từ lúc này trở đi thì help sẽ được mở như tôi đã trình bầy, và nhìn thấy lúc này là "Offline" chứ không là "Connected to Office.com"
help1.JPG

help2.JPG

help3.JPG
 
Upvote 0
Xin chào các bạn,
Tôi chạy đoạn code sau:

Mã:
Option Explicit

Sub TesDic()

    Const txt As String = "NhanVien 0001"
    Const tmp As String = "NhanVien 0000"
   
    Dim arr(), i As Long, j As Long
    Dim dic As Scripting.Dictionary
    Set dic = New Scripting.Dictionary
    'Kích hoạt microsoft scripting runtime

    dic.Add "NhanVien 0002", 10
    dic.Add "NhanVien 0003", 8
    dic.Add "NhanVien 0004", 6
    dic.Add tmp, 0
   
    If Not dic.Exists(txt) Then dic.Add txt, 100
    dic.Remove (tmp)
    dic(txt) = 120
   
    i = dic.Count
    ReDim arr(1 To i, 1 To 2)
   
    For j = 0 To i - 1
        arr(j + 1, 1) = dic.Keys(j)
        arr(j + 1, 2) = dic.Items(j)
    Next j
   
    Range("E2").Resize(UBound(arr), UBound(arr, 2)) = arr
   
End Sub

Kết quả OK, không có lỗi.
Nhưng khi thay đổi cách khai báo:
Mã:
Dim dic As Scripting.Dictionary
Set dic = New Scripting.Dictionary
Thành:
Mã:
Dim dic As Object
Set dic = CreateObject("Scripting.Dictionary")
Thì code báo lỗi:
"Property let procedure not defined and property get procedure did not return an object (Error 451)"

Nhờ các bạn chỉ giúp nguyên nhân ạ.
 
Lần chỉnh sửa cuối:
Upvote 0
Xin chào các bạn,
Tôi chạy đoạn code sau:

Mã:
Option Explicit

Sub TesDic()

    Const txt As String = "NhanVien 0001"
    Const tmp As String = "NhanVien 0000"
  
    Dim arr(), i As Long, j As Long
    Dim dic As Scripting.Dictionary
    Set dic = New Scripting.Dictionary
    'Kích hoạt microsoft scripting runtime

    dic.Add "NhanVien 0002", 10
    dic.Add "NhanVien 0003", 8
    dic.Add "NhanVien 0004", 6
    dic.Add tmp, 0
  
    If Not dic.Exists(txt) Then dic.Add txt, 100
    dic.Remove (tmp)
    dic(txt) = 120
  
    i = dic.Count
    ReDim arr(1 To i, 1 To 2)
  
    For j = 0 To i - 1
        arr(j + 1, 1) = dic.Keys(j)
        arr(j + 1, 2) = dic.Items(j)
    Next j
  
    Range("E2").Resize(UBound(arr), UBound(arr, 2)) = arr
  
End Sub

Kết quả OK, không có lỗi.
Nhưng khi thay đổi cách khai báo:
Mã:
Dim dic As Scripting.Dictionary
Set dic = New Scripting.Dictionary
Thành:
Mã:
Dim dic As Object
Set dic = CreateObject("Scripting.Dictionary")
Thì code báo lỗi:
"Property let procedure not defined and property get procedure did not return an object (Error 451)"

Nhờ các bạn chỉ giúp nguyên nhân ạ.
Nguyên nhân là khai báo dic muộn (late binding), còn trường hợp trên là khai báo dic sớm (early binding)
 
Upvote 0
Nguyên nhân là khai báo dic muộn (late binding), còn trường hợp trên là khai báo dic sớm (early binding)
Xin chào tam888,
Cảm ơn bạn đã giúp đỡ, vâng OT cũng hiểu sự khác nhau giữa khai báo sớm và khai báo muộn.
Vấn đề khai báo sớm hay khai báo muộn OT nghĩ nó tiện cho việc đỡ khỏi tích vào thư viện "microsoft scripting runtime" không nghĩ rằng nó lại ảnh hưởng đến nhiều vấn đề khác nữa.
Ví dụ như vấn đề OT đang thắc mắc.
 
Upvote 0
Xin chào tam888,
Cảm ơn bạn đã giúp đỡ, vâng OT cũng hiểu sự khác nhau giữa khai báo sớm và khai báo muộn.
Vấn đề khai báo sớm hay khai báo muộn OT nghĩ nó tiện cho việc đỡ khỏi tích vào thư viện "microsoft scripting runtime" không nghĩ rằng nó lại ảnh hưởng đến nhiều vấn đề khác nữa.
Ví dụ như vấn đề OT đang thắc mắc.
Ảnh hưởng này thì sửa được, bằng cách gán .Keys và .Items xuống 2 biến trước, rồi mới khai thác
Nguyên nhân thì chắc là lõi vấn đề trong xử lý khác nhau với khai báo sớm và khai báo muộn của Microsoft - code của họ đóng như hộp đen, nên chỉ có thể suy luận là do
- Khai báo sớm -- thì nó dành cho tạo vùng 1 đối tượng rõ ràng
- Khai báo muộn thì có thể chỉ là vùng ảo, nên khó cấp tiếp cho việc xác lập đối tượng mới Array (.keys, .items - là array)
(tuy thế tất cả là suy luận nên có thể không đúng bản chất)

Ảnh hưởng này thì sửa được, bằng cách gán .Keys và .Items xuống 2 biến trước, rồi mới khai thác
Cái này nên làm với cả 2 trường hợp Khai báo sớm và muộn vì có thể giúp chương trình chạy nhanh hơn, và logic hơn
Vì .Keys, :items --> về bản chất nó là phương thức --> có thể nó phải thực hiện tính toán trong đối tượng dic ==> như vậy mỗi lần gọi thì lại mất thời gian truy xuất.


Vấn đề khai báo sớm hay khai báo muộn OT nghĩ nó tiện cho việc đỡ khỏi tích vào thư viện "microsoft scripting runtime" không nghĩ rằng nó lại ảnh hưởng đến nhiều vấn đề khác nữa.
Còn khác nhau nhiều nữa, vì bản chất cách tạo khác nhau -- không chỉ khắc phục sự lười đâu. tất nhiên cả sự tiện nữa
 
Lần chỉnh sửa cuối:
Upvote 0
...
Nhưng khi thay đổi cách khai báo:
Mã:
Dim dic As Scripting.Dictionary
Set dic = New Scripting.Dictionary
Thành:
Mã:
Dim dic As Object
Set dic = CreateObject("Scripting.Dictionary")
Thì code báo lỗi:
"Property let procedure not defined and property get procedure did not return an object (Error 451)"

Nhờ các bạn chỉ giúp nguyên nhân ạ.
Bạn đã làm quen với VBA lâu ròi, và đã hỏi ở GPE nhiều rồi. Đáng lẽ bạn phải biết rằng "code báo lỗi .... " vẫn chưa đủ diễn tả hết.
Lần sau nhớ thêm chi tiết nó báo lỗi ở dòng nào. Tránh cho người khác phải đoán mò.

Theo code trên thì tôi đoán mò rằng nó lỗi ở dòng này:
arr(j + 1, 1) = dic.Keys(j)

Giải thích:
Vần đề này đã từng được một bạn khác (và tôi) giải thích trong một vài thớt về Dictionary. Hiển nhiên là bạn chưa đọc thớt ấy, hoặc có đọc nhưng chưa hiểu vì không có cơ hội thực tiễn.

Khi kết nối sớm, VBA có đủ tư liệu để đoán một số hàm/thủ tục và dùng dạng mặc định để chấp nhận một số ngữ pháp (qua kỹ thuật wapper hoặc hàm mặc định).
Khi kết nối trễ, VBA không có tư liệu để đoán, và vì vậy các hàm/thủ tục phải được gọi đúng ngữ pháp.

Hàm gọi keys của dictionary gọi theo đúng ngữ pháp là Keys(), và nó trả về một collection.
Ngữ pháp đúng thì phải là
arr(j + 1, 1) = dic.Keys()(j)
Và tương tự như vậy cho hàm Items()

Code trước của bạn không bị lỗi là vì khi kết nối sớm, VBA có đủ tư liệu để đoán hàm này và gọi cái wrapper property (thuộc tính giao diện) để hiểu dic.Keys(j) là cái gì.
 
Upvote 0
Xin chào các bạn,
Tôi chạy đoạn code sau:

Mã:
Option Explicit

Sub TesDic()

    Const txt As String = "NhanVien 0001"
    Const tmp As String = "NhanVien 0000"
  
    Dim arr(), i As Long, j As Long
    Dim dic As Scripting.Dictionary
    Set dic = New Scripting.Dictionary
    'Kích hoạt microsoft scripting runtime

    dic.Add "NhanVien 0002", 10
    dic.Add "NhanVien 0003", 8
    dic.Add "NhanVien 0004", 6
    dic.Add tmp, 0
  
    If Not dic.Exists(txt) Then dic.Add txt, 100
    dic.Remove (tmp)
    dic(txt) = 120
  
    i = dic.Count
    ReDim arr(1 To i, 1 To 2)
  
    For j = 0 To i - 1
        arr(j + 1, 1) = dic.Keys(j)
        arr(j + 1, 2) = dic.Items(j)
    Next j
  
    Range("E2").Resize(UBound(arr), UBound(arr, 2)) = arr
  
End Sub

Kết quả OK, không có lỗi.
Nhưng khi thay đổi cách khai báo:
Mã:
Dim dic As Scripting.Dictionary
Set dic = New Scripting.Dictionary
Thành:
Mã:
Dim dic As Object
Set dic = CreateObject("Scripting.Dictionary")
Thì code báo lỗi:
"Property let procedure not defined and property get procedure did not return an object (Error 451)"

Nhờ các bạn chỉ giúp nguyên nhân ạ.
Trong link sau tôi nói về server COM, kết nối sớm và kết nối trễ, sự khác nhau về bản chất của 2 loại kết nối.


Ở link dưới đây người ta hỏi vấn đề y như của bạn


Bạn đọc các bài viết của tôi trong 2 link sau thì bạn phải hiểu được nguyên nhân, và cách viết phải như thế nào. Và hiểu thêm về Dictionary.


-----------
Sau khi đọc các bài và hiểu thì bạn sẽ theo bài tôi hướng dẫn (bài đầu trong 2 link cuối) và sửa
Mã:
arr(j + 1, 1) = dic.Keys(j)
arr(j + 1, 2) = dic.Items(j)
thành
Mã:
arr(j + 1, 1) = dic.Keys()(j)
arr(j + 1, 2) = dic.Items()(j)
 
Upvote 0
Bạn đã làm quen với VBA lâu ròi, và đã hỏi ở GPE nhiều rồi. Đáng lẽ bạn phải biết rằng "code báo lỗi .... " vẫn chưa đủ diễn tả hết.
Lần sau nhớ thêm chi tiết nó báo lỗi ở dòng nào. Tránh cho người khác phải đoán mò.

Theo code trên thì tôi đoán mò rằng nó lỗi ở dòng này:
arr(j + 1, 1) = dic.Keys(j)

Giải thích:
Vần đề này đã từng được một bạn khác (và tôi) giải thích trong một vài thớt về Dictionary. Hiển nhiên là bạn chưa đọc thớt ấy, hoặc có đọc nhưng chưa hiểu vì không có cơ hội thực tiễn.

Khi kết nối sớm, VBA có đủ tư liệu để đoán một số hàm/thủ tục và dùng dạng mặc định để chấp nhận một số ngữ pháp (qua kỹ thuật wapper hoặc hàm mặc định).
Khi kết nối trễ, VBA không có tư liệu để đoán, và vì vậy các hàm/thủ tục phải được gọi đúng ngữ pháp.

Hàm gọi keys của dictionary gọi theo đúng ngữ pháp là Keys(), và nó trả về một collection.
Ngữ pháp đúng thì phải là
arr(j + 1, 1) = dic.Keys()(j)
Và tương tự như vậy cho hàm Items()

Code trước của bạn không bị lỗi là vì khi kết nối sớm, VBA có đủ tư liệu để đoán hàm này và gọi cái wrapper property (thuộc tính giao diện) để hiểu dic.Keys(j) là cái gì.

Xin chào Bác VetMini
Dạ đúng rồi code lỗi ở dòng:
arr(j + 1, 1) = dic.Keys(j)

Con cảm ơn Bác đã chỉ dẫn ạ, lần này chắc con phải đọc đi đọc lại vài lần những vấn đề con thắc mắc :D
Bài đã được tự động gộp:

Trong link sau tôi nói về server COM, kết nối sớm và kết nối trễ, sự khác nhau về bản chất của 2 loại kết nối.


Ở link dưới đây người ta hỏi vấn đề y như của bạn


Bạn đọc các bài viết của tôi trong 2 link sau thì bạn phải hiểu được nguyên nhân, và cách viết phải như thế nào. Và hiểu thêm về Dictionary.


-----------
Sau khi đọc các bài và hiểu thì bạn sẽ theo bài tôi hướng dẫn (bài đầu trong 2 link cuối) và sửa
Mã:
arr(j + 1, 1) = dic.Keys(j)
arr(j + 1, 2) = dic.Items(j)
thành
Mã:
arr(j + 1, 1) = dic.Keys()(j)
arr(j + 1, 2) = dic.Items()(j)

Xin chào Bác Siwtom,
Con cảm ơn Bác nhiều ạ, thực sự giờ con mới tìm hiểu về Dic và về code T_T
Không biết được bao lâu và sẽ đi đâu ạ.
Con chúc Bác nhiều sức khỏe.
 
Lần chỉnh sửa cuối:
Upvote 0
Mã:
Public Sub Supper_man()
Dim Rng As Range, xCell As Range
Dim xRows As Integer
xTitleId = "Supper_Trinh_ACC"
Set WorkRng = Application.Selection
Set WorkRng = Application.InputBox("Range", xTitleId, WorkRng.Address, Type:=8)
Application.ScreenUpdating = False
Application.DisplayAlerts = False
xRows = WorkRng.Rows.Count
For Each Rng In WorkRng.Columns
For i = 1 To xRows - 1
        For j = i + 1 To xRows
            If Rng.Cells(i, 1).Value <> Rng.Cells(j, 1).Value Then
                Exit For
            End If
        Next
        WorkRng.Parent.Range(Rng.Cells(i, 1), Rng.Cells(j - 1, 1)).Merge
        i = j - 1
    Next
Next
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
nhờ các thầy cô xem giúp em đoạn code trên với ạ. em muốn merge ô lại. nhưng các ô vẫn phải giữ giá trị như ban đầu. (lí do là em để em dùng sumproduct cho nó tính được ạ).
Và có thể giữ nguyên fomat và màu định dạng ban đầu không ạ
 

File đính kèm

  • aaaaaaa.xlsb
    14.4 KB · Đọc: 3
Lần chỉnh sửa cuối:
Upvote 0
Các bạn giúp mình đoạn VBA này với.

Mình muốn Copy 1 dòng sau đó chèn dòng đã copy vào các dòng phía dưới.
Mình tìm được đoạn code như dưới mà không biết làm tiếp như thế nào. Các bạn xem file đính kèm rồi giúp mình với nha. Thanks.

Mã:
Sub InsertCopiedCells()
    Rows("1:1").Select
    Selection.Copy
    Selection.Insert Shift:=x1Down
    Application.CutCopyMode = False
End Sub
 

File đính kèm

  • Insert Copied Cells.xlsm
    23.6 KB · Đọc: 4
Upvote 0
Các bạn giúp mình đoạn VBA này với.

Mình muốn Copy 1 dòng sau đó chèn dòng đã copy vào các dòng phía dưới.
Mình tìm được đoạn code như dưới mà không biết làm tiếp như thế nào. Các bạn xem file đính kèm rồi giúp mình với nha. Cảm ơn.

Mã:
Sub InsertCopiedCells()
    Rows("1:1").Select
    Selection.Copy
    Selection.Insert Shift:=x1Down
    Application.CutCopyMode = False
End Sub
Ơ mới có copy chưa có paste.
 
Upvote 0
Rows("1:1").Select ' => chọn dòng 1
Selection.Copy ' => Copy dòng đang chọn
Selection.Insert Shift:=x1Down ' => chèn tại dòng đang chọn

Nếu muốn chèn tại dòng 3 thì viết rút gọn như sau:

Rows("1:1").Copy ' => Copy dòng 1
Rows("3:3").Insert Shift:=x1Down ' => chèn tại dòng 3
 
Upvote 0
Web KT
Back
Top Bottom