Các câu đố, bài tập nhằm ôn tập & bổ sung kiến thức căn bản VBA (1 người xem)

Liên hệ QC

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

Hoàng Trọng Nghĩa

Chuyên gia GPE
Thành viên BQT
Moderator
Tham gia
17/8/08
Bài viết
8,662
Được thích
16,725
Giới tính
Nam
Với tinh thần chơi mà học, học mà chơi, nên tôi đã mở ra topic này, hy vọng các thành viên tham gia, nhất là các thành viên mới biết về VBA.

Sau đây là câu hỏi đầu tiên:

Câu hỏi 1: Bằng phương pháp nào nhanh nhất để tìm ra ô nào trong một cột chứa một điều kiện.

Tôi có 1 file Excel 2007, với cột A, từ A1 đến A1048576 đều có giá trị.

Bằng phương pháp nào nhanh nhất (dùng mảng, dùng For Each v.v...) để tìm ra ô nào trong cột A chứa chữ "Nghia", đồng thời với ô ở cột B tương ứng nhập giá trị "OK" vào đó?

Ví dụ tìm thấy trong ô A2 có giá trị là "Nghia" thì ô B2 nhập vào "OK".

Hiện tại, đáp án nhanh nhất mà tôi có được đã gửi mail riêng (nhằm ghi lại thời gian gửi, để tránh nói ăn gian).

Để tiện việc theo dõi các câu đố, các bài tập tôi đã tạo ra topic này các bạn click vào đây:

Các link của topic "Các câu đố, bài tập nhằm ôn tập & bổ sung kiến thức căn bản VBA"
 

File đính kèm

Lần chỉnh sửa cuối:
Anh ptm0412 góp ý cho bạn là đúng rồi. Bạn nên dùng Do ... Loop.

Tôi hơi tò mò. Chả nhẽ bạn có thói quen biến mọi vòng Do ... Loop thành vòng For ... Next?
Anh ptm0412 đã buông tha cho bạn nhưng tôi thử "đeo bám" chút.

Dùng Do ... Loop hay For ... Next trong trường hợp bài này thì đều phải kiểm tra đk để ra khỏi vòng lặp. Tức thao tác kiểm tra đk là như nhau. Nhưng với Do ... Loop thì chỉ có duy nhất thao tác đã nói ở trên, nếu đk không thỏa thì lại "chơi" tiếp vòng mới. Còn với For ... Next thì bạn hãy để ý là khi không thực hiện Exit For thì sao? Thì sẽ có 2 thao tác phải làm thêm:

1. Tăng i thêm 1
2. Kiểm tra xem i có vượt quá cận trên không.

Như vậy nếu có 1000 ô có Nghia thì sẽ có 2000 thao tác thêm so với dùng Do ... Loop. Nếu có 10000 ô Nghia thì có 20000 thao tác phải làm thêm. Những thao tác đó cần nhiều hay ít "điện nước" tôi không bàn nhưng rõ ràng đó là những thao tác thừa. Mà bạn đang tiết kiệm thời gian từng "chớp mắt", đúng không?

Mà bài #49 code hơi lủng củng. Trước hết phải cho Find ra khỏi vòng lặp. Find chỉ gọi 1 lần mà thôi. Chả nhẽ vòng lặp chạy 1000 lần (có 1000 ô Nghia) thì thực hiện Find 1000 lần? Bạn hãy thử dùng Do ... Loop. Chắc chắn code sẽ "đẹp" hơn.


Sao vậy Thầy? Nếu range có 5 ô (B1:B5) và cả 5 ô có giá trị "Nghia" thì nó tìm 5 lần, nguyên tắc của nó là:

i = 1: [B1:B5].find, nếu giá trị có tại ô B1 thì Next của nó sẽ là B2

i = 2: [B2:B5].find, nếu giá trị có tại ô B2 thì Next của nó sẽ là B3

lần lượt cho đến:

i = 5: [B5:B5].find, nếu giá trị có tại ô B5 thì Next của nó sẽ là B1

1) Nó có thể tiếp tục lặp nữa không nếu ta cho For i = 1 To 5?

2) Giả sử ta For i = 1 To 1 tỷ đi chăng nữa, ta đã chặn ngay khi nó có quay lại ô đầu tiên rồi còn gì? (If fRng.Row < j Then Exit For)

Chẳng hiểu các Thầy nghĩ làm sao nữa? Các Thầy làm ơn thử với dữ liệu nhỏ chỉ cần ghi dữ liệu vài dòng, nhưng với range tham chiếu Range("A1:A1048576") thử xem! Find Method nó dường như không phải là chạy theo kiểu vòng lặp nên nó nhanh đến kinh người!

À, vấn đề Do ... Loop chưa chắc các bạn mới đã học, vì chỉ có thể có For ... Next, làm việc với Do ... Loop thì code sẽ gọn hơn nhiều!


Mã:
Sub DoLoop()
    Dim fRng As Range, lRng As Range
    With Sheet1.Range("A1:A1048576")
        Set fRng = .Find(What:="Nghia", LookIn:=xlFormulas, LookAt:=xlWhole)
        If fRng Is Nothing Then
            Exit Sub
        Else
            Dim r As Long, m As Long
            fRng.Offset(, 1) = "OK"
            Set lRng = fRng
            r = lRng.Row
            Do
                [COLOR=#0000ff]''Dem so lan lap:
                m = m + 1
                MsgBox m
                [/COLOR]
                Set fRng = .Find(What:="Nghia", After:=lRng, LookIn:=xlFormulas, LookAt:=xlWhole)
                .FindNext(After:=lRng).Offset(, 1) = "OK"
                Set lRng = fRng
            Loop While fRng.Row > r
        End If
    End With
End Sub

Ta thấy dù là For ... Next hay Do ... Loop thì số lần lặp cũng như nhau mà thôi!
 
Lần chỉnh sửa cuối:
Upvote 0
Ta thấy dù là For ... Next hay Do ... Loop thì số lần lặp cũng như nhau mà thôi!

Tức là: Hình như Nghĩa chưa viết code liên quan đến Find Method bao giờ?
Vì phương pháp tổng quát người ta không phải viết kiểu như vậy!
Anh Bill đã tổng hợp trong Help ấy. Nghĩa vào của sổ VBA, gõ đại chử FindNext ở đâu đó, bôi đen nó rồi bấm F1 sẽ có câu trả lời
Tin chắc code đó chính là tối ưu nhất, giải thuật đại khái thế này:
- Find giá trị đầu tiên trước trong vùng rng (nằm ngoài vòng lập), giả định là biến rFind (rFind = rng.Find(...))
- Phát biểu IF, nếu tìm thấy thì đi tiếp: (If Not rFind ís Nothing then....)
Đặt 1 biến String là địa chỉ đầu tiên tìm thấy (firstAddress = rFind.Address)
*Vào vòng lập Do
* Làm gì đó...
* Và Set rFind = rng.FindNext(rFind)
*Loop Until rFind.Address = firstAddress
- End If
--------------
Đại khái thế
 
Lần chỉnh sửa cuối:
Upvote 0
Sao vậy Thầy? Nếu range có 5 ô (B1:B5) và cả 5 ô có giá trị "Nghia" thì nó tìm 5 lần, nguyên tắc của nó là:

i = 1: [B1:B5].find, nếu giá trị có tại ô B1 thì Next của nó sẽ là B2

i = 2: [B2:B5].find, nếu giá trị có tại ô B2 thì Next của nó sẽ là B3

lần lượt cho đến:

i = 5: [B5:B5].find, nếu giá trị có tại ô B5 thì Next của nó sẽ là B1

1) Nó có thể tiếp tục lặp nữa không nếu ta cho For i = 1 To 5?

2) Giả sử ta For i = 1 To 1 tỷ đi chăng nữa, ta đã chặn ngay khi nó có quay lại ô đầu tiên rồi còn gì? (If fRng.Row < j Then Exit For)

Chẳng hiểu các Thầy nghĩ làm sao nữa? Các Thầy làm ơn thử với dữ liệu nhỏ chỉ cần ghi dữ liệu vài dòng, nhưng với range tham chiếu Range("A1:A1048576") thử xem! Find Method nó dường như không phải là chạy theo kiểu vòng lặp nên nó nhanh đến kinh người!

Bạn đọc lại bài của tôi. Có chỗ nào tôi nói là nếu có 5 Nghia thì vẫn có nhiều hơn 5 vòng lặp???????????????????????????

Tôi có bàn về số vòng lặp hơn hay kém đâu?

Cái tôi nói là 2 thao tác thêm và Find phải đưa ra ngoài vòng lặp ... Do ... Loop hay For ... Next thì Find chỉ cần gọi 1 lần mà thôi.

Hay tôi viết khó hiểu quá?

Tất nhiên tôi chỉ có ý kiến chủ quan thôi chứ bạn có quyền viết theo ý bạn.
 
Upvote 0
bản chất :
Vòng lặp for --> biết trước số lần lặp
While ..Wend --> không biết trước số lần lặp
---------------------------------------------
Kết quả của 2 cú pháp trên : i = 11
---------------------------------------------
1 điểm khác biệt nữa là dùng cú pháp While... Wend ta sẽ không thể thoát khỏi vòng lặp bằng lệnh như kiểu exit do, exit for được
----------------------------------------------

Xin lỗi, thói quen tai hại. Cú pháp while-wend lỗi thời rồi, đáng lẽ phải đào thải. Xin sửa lại là
DO WHILE (điều kiện)
' code ở đây
LOOP

Bạn trả lời về số lần lặp là đúng đường rồi. Xin diễn thêm một chút nữa.
 
Upvote 0
Bạn đọc lại bài của tôi. Có chỗ nào tôi nói là nếu có 5 Nghia thì vẫn có nhiều hơn 5 vòng lặp???????????????????????????

Tôi có bàn về số vòng lặp hơn hay kém đâu?

Cái tôi nói là 2 thao tác thêm và Find phải đưa ra ngoài vòng lặp ... Do ... Loop hay For ... Next thì Find chỉ cần gọi 1 lần mà thôi.

Hay tôi viết khó hiểu quá?

Tất nhiên tôi chỉ có ý kiến chủ quan thôi chứ bạn có quyền viết theo ý bạn.


Em đã thử với 2 thủ tục này:

For ... Next:

Mã:
Private Sub FindNext()
    Dim t As Double
    t = Timer
    Dim i As Long, fRng As Range, lRng As Range
    With Sheet1.Range("A1:A1048576")
        Set fRng = .Find(What:="Nghia", LookIn:=xlFormulas, LookAt:=xlWhole)
        If fRng Is Nothing Then
            Exit Sub
        Else
            Dim r As Long
            fRng.Offset(, 1) = "OK"
            Set lRng = fRng
            r = lRng.Row
            For i = 1 To 1000000000
                Set fRng = .Find(What:="Nghia", After:=lRng, LookIn:=xlFormulas, LookAt:=xlWhole)
                .FindNext(After:=lRng).Offset(, 1) = "OK"
                If fRng.Row = r Then Exit For
                Set lRng = fRng
            Next
        End If
    End With
    Debug.Print Timer - t
End Sub

Và Do ... Loop:

Mã:
Sub DoLoop()
    Dim t As Double
    t = Timer
    Dim fRng As Range, lRng As Range
    With Sheet1.Range("A1:A1048576")
        Set fRng = .Find(What:="Nghia", LookIn:=xlFormulas, LookAt:=xlWhole)
        If fRng Is Nothing Then
            Exit Sub
        Else
            Dim r As Long
            fRng.Offset(, 1) = "OK"
            Set lRng = fRng
            r = lRng.Row
            Do
                Set fRng = .Find(What:="Nghia", After:=lRng, LookIn:=xlFormulas, LookAt:=xlWhole)
                .FindNext(After:=lRng).Offset(, 1) = "OK"
                Set lRng = fRng
            Loop While fRng.Row > r
        End If
    End With
    Debug.Print Timer - t
End Sub

Sau khi chạy thử tại dữ liệu ở File bài đầu tiên, em có kết quả là For ... Next chạy nhanh hơn đấy!




Cái tôi nói là 2 thao tác thêm và Find phải đưa ra ngoài vòng lặp ... Do ... Loop hay For ... Next thì Find chỉ cần gọi 1 lần mà thôi.

Thôi, Thầy thử làm một thủ tục như Thầy nói để cho em học hỏi vậy!
 
Upvote 0
Bạn đọc lại bài của tôi. Có chỗ nào tôi nói là nếu có 5 Nghia thì vẫn có nhiều hơn 5 vòng lặp???????????????????????????

Tôi có bàn về số vòng lặp hơn hay kém đâu?

Cái tôi nói là 2 thao tác thêm và Find phải đưa ra ngoài vòng lặp ... Do ... Loop hay For ... Next thì Find chỉ cần gọi 1 lần mà thôi.

Hay tôi viết khó hiểu quá?

Tất nhiên tôi chỉ có ý kiến chủ quan thôi chứ bạn có quyền viết theo ý bạn.
Ah, em hiểu ý Thầy rồi, phải chăng Thầy muốn nói đại loại là như thế này:

Mã:
Sub DoLoop1()
    Dim t As Double
    t = Timer
    Dim fRng As Range, lRng As Range
    With Sheet1.Range("A1:A1048576")
        Set fRng = .Find(What:="Nghia", LookIn:=xlFormulas, LookAt:=xlWhole)
        If fRng Is Nothing Then
            Exit Sub
        Else
            Dim r As Long
            fRng.Offset(, 1) = "OK"
            Set lRng = fRng
            r = lRng.Row
[COLOR=#ff0000]            Set fRng = .Find(What:="Nghia", After:=lRng, LookIn:=xlFormulas, LookAt:=xlWhole)[/COLOR]
            Do
                .FindNext(After:=lRng).Offset(, 1) = "OK"
                Set lRng = .FindNext(After:=lRng)
            Loop While lRng.Row > r
        End If
    End With
    Debug.Print Timer - t
End Sub
 
Upvote 0
Anh ptm0412 góp ý cho bạn là đúng rồi. Bạn nên dùng Do ... Loop.

Tôi hơi tò mò. Chả nhẽ bạn có thói quen biến mọi vòng Do ... Loop thành vòng For ... Next?
Anh ptm0412 đã buông tha cho bạn nhưng tôi thử "đeo bám" chút.

Tôi chưa tha (ẹc ẹc), chẳng qua là bận họp hành đến hết giờ luôn.

Tôi ghi rõ là "nếu ô đầu tiên không phải là Nghia, fRng.Row không phải 1, thì không thoát khỏi vòng lặp, và phải chạy đủ 1 triệu lần"

Vậy thì, code sau khi sửa, giảm từ 1 triệu xuống 6 lần. chứ không phải tăng từ 5 lên 6.

Xin thưa với Sư phụ, dòng màu đỏ là không chính xác! Code đầu do nó không tính tới hàng 1 nên nếu số hàng có "Nghia" là 5 thì nó lặp đúng 5 lần chứ không phải là 6 lần như đã sửa ở code sau đâu Sư phụ!

(Máu xanh hay đỏ gì, tôi cũng không sai)

Ngoài ra tôi còn nói: Nhưng biến i cũng vẫn không được sử dụng!

và Nghĩa phản biện: Ta đâu cần sử dụng biến i làm gì! Miễn số vòng lặp tối thiểu bằng tổng số hàng mà chúng duyệt qua là được, đừng nhỏ hơn sẽ bị thiếu nếu dữ liệu 100% có từ "Nghia".

Thì đây: Anh siwtom nói 2 việc không có câu lệnh vẫn phải thực hiện là tăng i lên 1 và so sánh i với 1 triệu. Và 2 động tác này làm 1 triệu lần. Một biến không xài mà phải làm thêm 2 động tác cho mỗi vòng lặp, vậy có nên dùng không?

Nếu đúng chuẩn như bài ndu gợi ý thì 1 Find nằm ngoài và 1 findnext nằm trong vòng lặp.

Thử xét đoạn code sau (trong vòng lặp Do của Nghĩa, bài #102):

Mã:
Set fRng = .Find(What:="Nghia", After:=lRng, LookIn:=xlFormulas, LookAt:=xlWhole)                 
     .FindNext(After:=lRng).Offset(, 1) = "OK"

1. 1 cái Find và 1 cái FindNext
2. fRng và kết quả FindNext cùng là 1 ô mà phải Find 2 lần? Tại sao câu thứ nhì không phải là fRng.Offset(0,1) = "OK"?

Mà cũng phải, vì cái topic "Tổng hợp về phương thức Find" Nghĩa chưa đọc.
 
Lần chỉnh sửa cuối:
Upvote 0
Bây giờ tôi sửa lại code FindNext nè, quay macro lại và sửa theo cách vận hành của nó!

Với Do ... Loop:

Mã:
Sub DoLoopNew()
    Dim t As Double
    Range("B:B").Clear
    t = Timer
    Dim FindRng As Range
    With Sheet1.Range("A1:A1048576")
        Set FindRng = .Find(What:="Nghia", LookIn:=xlFormulas, LookAt:=xlWhole)
        If FindRng Is Nothing Then
            Exit Sub
        Else
            Dim FstRng As String, CurRng As String
            FindRng.Offset(, 1) = "OK"
            FstRng = FindRng.Address
            CurRng = FstRng
            Set FindRng = .Find(What:="Nghia", After:=Range(CurRng), LookIn:=xlFormulas, LookAt:=xlWhole)
            Do
                With .FindNext(After:=Range(CurRng))
                    .Offset(, 1) = "OK"
                    CurRng = .Address
                End With
            Loop While CurRng <> FstRng
        End If
    End With
    Debug.Print Timer - t
End Sub

Với For ... Next:

Mã:
Private Sub FindNextNew()
    Dim t As Double
    Range("B:B").Clear
    t = Timer
    Dim FindRng As Range
    With Sheet1.Range("A1:A1048576")
        Set FindRng = .Find(What:="Nghia", LookIn:=xlFormulas, LookAt:=xlWhole)
        If FindRng Is Nothing Then
            Exit Sub
        Else
            Dim i As Long, FstRng As String, CurRng As String
            FindRng.Offset(, 1) = "OK"
            FstRng = FindRng.Address
            CurRng = FstRng
            Set FindRng = .Find(What:="Nghia", After:=Range(CurRng), LookIn:=xlFormulas, LookAt:=xlWhole)
            For i = 1 To 1048576
                With .FindNext(After:=Range(CurRng))
                    .Offset(, 1) = "OK"
                    CurRng = .Address
                End With
                If CurRng = FstRng Then Exit For
            Next
        End If
    End With
    Debug.Print Timer - t
End Sub

Không biết trên máy của các bạn như thế nào, nhưng đo thời gian cho hơn triệu dòng thì tốc độ 2 thằng em này tương đương đấy!
 
Upvote 0
Bây giờ tôi sửa lại code FindNext nè, quay macro lại và sửa theo cách vận hành của nó!


Không biết trên máy của các bạn như thế nào, nhưng đo thời gian cho hơn triệu dòng thì tốc độ 2 thằng em này tương đương đấy!

Ai viết code FindNext kỳ cục vậy chứ
Nó vầy:
Mã:
Sub NDU()
  Dim sAddress As String, rFind As Range
  Dim t As Double
  Range("B:B").Clear
  t = Timer
  With Sheet1.Range("A1:A1048576")
    Set rFind = .Find("Nghia", , xlFormulas, xlWhole)
    If Not rFind Is Nothing Then
      sAddress = rFind.Address
       Do
         rFind.Offset(, 1) = "OK"
         Set rFind = .FindNext(rFind)
      Loop Until rFind.Address = sAddress
    End If
  End With
  Debug.Print Timer - t
End Sub
Chưa nói đến tốc độ thì code của đồng chí vẫn rất.. kỳ cục
Bởi vậy tôi nghi ngờ rằng từ trước giờ đồng chí chưa viết code FindNext bao giờ
Ẹc... Ẹc...
 
Upvote 0
Bởi vậy tôi nghi ngờ rằng từ trước giờ đồng chí chưa viết code FindNext bao giờ
Ẹc... Ẹc...

Đúng là trước giờ không bao giờ tìm kiếm bằng FindNext! Chỉ mới làm thử sáng qua thôi ("quá độ đi lên" Array để tìm kiếm, nên đốt giai đoạn thực hiện trên Range đó mà). Tuy nhiên, vẫn thấy rằng làm trên Array luôn đảm bảo nhanh, nhất là với dữ liệu lớn!
 
Lần chỉnh sửa cuối:
Upvote 0
Ah, em hiểu ý Thầy rồi, phải chăng Thầy muốn nói đại loại là như thế này:

"Phải chăng"? Tôi nghĩ là tôi đã viết rất rõ mà. Cả ở bài đầu lẫn bài thứ 2. Trích

Mã:
[B][COLOR=#ff0000]Find phải đưa ra ngoài vòng lặp[/COLOR][/B] ... [COLOR=#0000ff]Do ... Loop[/COLOR] hay [COLOR=#0000ff]For ... Next[/COLOR] thì [B][COLOR=#ff0000]Find chỉ cần gọi 1 lần mà thôi[/COLOR][/B].

Mã:
Sub DoLoop1()
    Dim t As Double
    t = Timer
    Dim fRng As Range, lRng As Range
    With Sheet1.Range("A1:A1048576")
        Set fRng = .Find(What:="Nghia", LookIn:=xlFormulas, LookAt:=xlWhole)
        If fRng Is Nothing Then
            Exit Sub
        Else
            Dim r As Long
            fRng.Offset(, 1) = "OK"
            Set lRng = fRng
            r = lRng.Row
[COLOR=#ff0000]            Set fRng = .Find(What:="Nghia", After:=lRng, LookIn:=xlFormulas, LookAt:=xlWhole)[/COLOR]
            Do
                .FindNext(After:=lRng).Offset(, 1) = "OK"
                Set lRng = .FindNext(After:=lRng)
            Loop While lRng.Row > r
        End If
    End With
    Debug.Print Timer - t
End Sub

Vẫn chưa chuẩn. Tại sao bạn phải gọi Find 2 lần? Một lần trước IF và một lần trước Do?
Ngoài ra trong vòng lặp bạn gọi FindNext 2 lần. Làm lại. Vì bài như thế thì thầy ptm0412 chắc chắn không nhận.
 
Lần chỉnh sửa cuối:
Upvote 0
Căn bản mà như thế này thì làm sao em có thể tiếp thu nổi.
 
Upvote 0
Nếu chúng ta cho rằng mảng luôn nhanh hơn range trong mọi trường hợp thì có thể chưa chắc đúng 100%. Giả sử ta có dữ liệu cần tìm nằm ở ô A1 và IV65536, thì thử đưa toàn bộ giá trị của Cells vào 1 mảng rồi dùng vòng lặp duyệt qua để tìm kiếm thì sẽ mất bao lâu so với dùng FindNext để tìm. Theo mình thì sự chênh lệch khá lớn.
 
Upvote 0
Càng ngày topic càng xa với mục đích:


Mấy trang gần đây lại có thêm các bài tranh luận của các bậc thầy nữa kia đấy!

Phát sợ & ngơ ngẩn luôn!
 
Upvote 0
Vẫn chưa chuẩn. Tại sao bạn phải gọi Find 2 lần? Một lần trước IF và một lần trước Do?
Ngoài ra trong vòng lặp bạn gọi FindNext 2 lần. Làm lại. Vì bài như thế thì thầy ptm0412 chắc chắn không nhận.

Nhờ các Thầy góp ý em nhận ra 3 vấn đề:

1) Dù là vòng lặp không sử dụng giá trị i, nhưng do ta khai báo biến thì khi giả sử i chạy đến 1 triệu dòng thì nó cũng chiếm 1 lượng lớn trong biến Long. Vì thế, khi không sử dụng biến i thì nên dùng Do ... Loop sẽ hợp lý hơn.

2) Do mới thử nghiệm quay macro, nó cứ lấn cấn vụ After:=ActiveCell làm cho mình tưởng phải dùng Find đầu tính ra cái ActiveCell và cái Find thứ 2 căn cứ vào cái Find đầu nên tính tiếp. Nhưng sau khi code của Thầy ndu96081631 đưa lên thì em mới "giật mình", tự hỏi sao FindNext nó đơn giản đến như vậy mà cứ phải suy diễn đâu đâu, còn dám "cải Thầy" nữa nên bị "núi đè"!

3) Tự phạt "quỳ gai mít"!

-------------------------------------------------------

Căn bản mà như thế này thì làm sao em có thể tiếp thu nổi.

Khi học căn bản thông thường người ta sẽ học quay macro các thao tác trên Range, FindMethod cũng vậy, từ đó ta sửa lại theo ý của ta (dỡ hay là hay ở chỗ này), cho nên đây là một trong những vấn đề căn bản nhất của người lập trình, bạn không biết có thể ghi macro sẽ thấy thôi!

Chẳng hạn thế này:

Mã:
Sub Macro1()
    Cells.Find(What:="Nghia", After:=ActiveCell, LookIn:=xlFormulas, LookAt _
        :=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
        False, SearchFormat:=False).Activate
    Cells.FindNext(After:=ActiveCell).Activate
    Cells.FindNext(After:=ActiveCell).Activate
    Cells.FindNext(After:=ActiveCell).Activate
    Cells.FindNext(After:=ActiveCell).Activate
End Sub


Sau đó chỉnh lại như các Thầy để áp dụng cho kiến thức cua mình là chắc ăn!

-------------------------------------------------------
P/s: Do một số thành viên "than" khó quá (không biết đâu là dễ cho họ), kính nhờ SMOD dời các bài liên quan đến FindMethod qua một chủ đề khác ạ. Xin cảm ơn.
 
Upvote 0
Nếu chúng ta cho rằng mảng luôn nhanh hơn range trong mọi trường hợp thì có thể chưa chắc đúng 100%. Giả sử ta có dữ liệu cần tìm nằm ở ô A1 và IV65536, thì thử đưa toàn bộ giá trị của Cells vào 1 mảng rồi dùng vòng lặp duyệt qua để tìm kiếm thì sẽ mất bao lâu so với dùng FindNext để tìm. Theo mình thì sự chênh lệch khá lớn.

Tôi đã có nói qua. Muốn thử hiệu quả của code trong tình trạng này thì phải đặt ra một phương án thử, vẽ biểu đồ kết quả theo từng loại dữ liệu đàng hoàng.

Càng ngày topic càng xa với mục đích:

Mấy trang gần đây lại có thêm các bài tranh luận của các bậc thầy nữa kia đấy!

Phát sợ & ngơ ngẩn luôn!

Chịu khó đọc kỹ sẽ thấy giữa những cái "nguy nga tráng lệ" có những cái rất đơn giản.

Tôi rất ít khi chỉ dẫn code từ đầu đến cuối. Thường thì tôi chỉ dẫn kỹ thuật.

Nếu bạn chịu khó phân tách thì sẽ thấy những gì tôi nêu ra chỉ thuộc về phương pháp lập trình. Những cái này nếu nắm vững sẽ có lợi cho bạn về sau. (vd kỹ thuật duyệt mảng song song)

Những cái cao cấp khác thuộc về cách thức sử dụng công cụ được cung cấp VBA. Những cái này thì chỉ khi nào muốn chuyên sâu vấn đề mới cần học. Nếu bạn để ý sẽ thấy tôi chỉ đề cập sơ qua chứ không hề nói thêm (vd Data Object)

Người mới học thì cũng nên biết mạng net là cả một rừng tin tức. Lọc ra được cái hợp với mình cũng là một kỹ năng cần thiết cho người học hỏi.

Căn bản mà như thế này thì làm sao em có thể tiếp thu nổi.

Đọc 10 bài viết, hiểu được 1 bài là đã tiếp thu nhiều lắm rồi.
Nếu một ngày bạn đọc 10 bài. Một năm lọc ra được 360 bài. Đủ để thành tay lão luyện.
 
Lần chỉnh sửa cuối:
Upvote 0
Cũng vì [] là một Evaluate, nên tôi nói rộng ra một chút cho các bạn dùng mảng hiểu được tại sao ta bị lỗi!

Với Range() ta sử dụng thế nào cũng được:

Mã:
Sub Macro7()
    Dim Arr()
    [COLOR=#0000ff]Arr = Range("B1:B6")[/COLOR][B][COLOR=#ff0000].Value[/COLOR][/B]
''Hoặc:
[COLOR=#0000ff]    ''Arr = Range("B1:B6")[/COLOR]
End Sub

Nhưng với [] thì không được như vậy, sẽ bị lỗi trong trường hợp này:

Mã:
Sub Macro5()
    Dim Arr()
[COLOR=#0000ff]    Arr = [B1:B6][/COLOR]
End Sub

Tức là bị lỗi khi thiếu .Value. Cho nên ta chỉ được sử dụng chúng như thế này:

Mã:
Sub Macro6()
    Dim Arr()
[COLOR=#0000ff]    Arr = [B1:B6][/COLOR][B][COLOR=#FF0000].Value[/COLOR][/B]
End Sub



VBA cũng thật là rối rắm phải không các bạn?!
Qua bài này mình cũng ngộ ra 1 điều là khai lại Dim Arr (hoặc Dim Arr as Variant) thì có .Value hay không vẫn ok
 
Upvote 0
Càng ngày topic càng xa với mục đích:


Mấy trang gần đây lại có thêm các bài tranh luận của các bậc thầy nữa kia đấy!

Phát sợ & ngơ ngẩn luôn!
những bài trên chưa có bài nào là tranh luận cả "tranh luận có nén đá nén gạch --->vỡ đầu bể trán"
những bài ấy chỉ là bàn luận và nhận xét " rút ra tinh tuý chất lượng " bài viết mà thôi

theo mình 2 từ căn bản không chỉ ở những cái a b c gì gì đó, mà nó gồm cả những cái công cụ thường dùng trong công việc thì nó là căn bản "đó chỉ là quan điểm của cá nhân mà thôi "
 
Upvote 0
Câu hỏi 6: Sau khi tự tạo một Sub, bạn gán phím tắt thế nào?

Khi các bạn mới tập tành viết code, để chạy code của mình thì tại môi trường VBA bạn đặt con trỏ vào ngay trong code đó và nhấn F5 hoặc nhấn nút Run Sub/UserForm trên thanh công cụ.

Thế thì chẳng lẽ mỗi lần chạy code mỗi lần phải vào VBA hay sao? Phải tạo phím tắt cho nó thôi.

Câu hỏi đặt ra là bạn sẽ tạo nó như thế nào? Mời các "tềnh iu" mới tập tành code trả lời!
 
Lần chỉnh sửa cuối:
Upvote 0
Người mới học thì cũng nên biết mạng net là cả một rừng tin tức. Lọc ra được cái hợp với mình cũng là một kỹ năng cần thiết cho người học hỏi.

Giữa NET mà bạn nói với 1 topic trên GPE mà tôi đang đề cập đến là 1 trời 1 vực.

Ở topic này, từng thành viên cộng đồng cũng có thể đọc & tìm ra những cái mình cần, như bạn nói.

Nhưng với cộng đồng này thì không vậy, vơí những lí do sau:

Bài này, cũng như mọi bài khác trên diễn đàn, có tiêu đề & yêu cầu của GPE.COM là tiêu đề luôn phải bám vào nội dung chính của bài.
(Kể cả bài này tôi viết cũng chưa bám vào tiêu đề & có thể được xoá đi bất cứ lúc nào) & những bài xa với tiêu đề của bài nên xoá đi.


Những bài trên chưa có bài nào là tranh luận cả "tranh luận có nén đá nén gạch --->vỡ đầu bể trán"
những bài ấy chỉ là bàn luận và nhận xét " rút ra tinh tuý chất lượng " bài viết mà thôi

theo mình 2 từ căn bản không chỉ ở những cái a b c gì gì đó, mà nó gồm cả những cái công cụ thường dùng trong công việc thì nó là căn bản "đó chỉ là quan điểm của cá nhân mà thôi "

Tranh luận theo mình hiểu không phải là cãi nhau. Ở đây đã có nhiều bài tranh luận về học thuật. Đó là tranh luận nên có, nhưng không nên ở topic này.

Đúng là căn bản của toán học cao cấp khác xa với căn bản của toán học sơ cấp.
Nhưng tôi hiểu căn bản VBA thì không thể nhiều & đúng như bạn nói: Tuỳ mỗi người!
 
Lần chỉnh sửa cuối:
Upvote 0
Khi các bạn mới tập tành viết code, để chạy code của mình thì tại môi trường VBA bạn đặt con trỏ vào ngay trong code đó và nhấn F5 hoặc nhấn nút Run Sub/UserForm trên thanh công cụ.

Thế thì chẳng lẽ mỗi lần chạy code mỗi lần phải vào VBA hay sao? Phải tạo phím tắt cho nó thôi.

Câu hỏi đặt ra là bạn sẽ tạo nó như thế nào? Mời các "tềnh iu" mới tập tành code trả lời!
[hide]Tạo 1 nút, có thể là Button hoặc CammandButton và đặt code trong đó, có thể là Shapes, Pictures rồi Assign Marco ... đến Sub.[/hide]
Anh Nghĩa hỏi Admin kỹ thuật xem có thể tạo Tag HIDE để đặt câu trả lời vào trong đó hay không? Em thấy một số diễn đàn có sử dụng Tag này (chỉ Smod/mod mới thấy được). Như thế mới có nhiều đáp áp.
 
Upvote 0
Càng ngày topic càng xa với mục đích:
Mấy trang gần đây lại có thêm các bài tranh luận của các bậc thầy nữa kia đấy!
Phát sợ & ngơ ngẩn luôn!

Các bài tranh luận của tôi liên quan đến các vấn đề:

- Khi nào Do loop và khi nào For Next
- Trong For next biến i phải chịu 2 tác động: tăng 1 (hoặc tăng step), sau đó so sánh với giá trị cuối
- Viết câu lệnh thừa trong và ngoài vòng lặp
- Điều kiện thoát For Next: nếu điều kiện không thỏa thì chạy đủ số vòng lặp quy định.
- Điều kiện thoát Do loop: nếu điều kiện thoát không thỏa thì lặp vô tận.

Bạn đọc kỹ sẽ thấy chẳng có gì cao siêu vượt quá mức căn bản thấp nhất. Lớp căn bản GPE vừa kết thúc đã có các nội dung này.
 
Upvote 0
Khi các bạn mới tập tành viết code, để chạy code của mình thì tại môi trường VBA bạn đặt con trỏ vào ngay trong code đó và nhấn F5 hoặc nhấn nút Run Sub/UserForm trên thanh công cụ.

Thế thì chẳng lẽ mỗi lần chạy code mỗi lần phải vào VBA hay sao? Phải tạo phím tắt cho nó thôi.

Câu hỏi đặt ra là bạn sẽ tạo nó như thế nào? Mời các "tềnh iu" mới tập tành code trả lời!
Cái này mình biết nè: Nhấn ALT+F8, chọn macro cần tạo phím nóng, chọn options, phần shortcut key gõ vào kí tự mình muốn tạo phím nóng. Ví dụ mình gõ w vào ô đó rồi nhấn ok. Sau này muốn chạy macro đó thì mình nhấn tổ hợp phím ctrl+w
 
Lần chỉnh sửa cuối:
Upvote 0
Càng ngày topic càng xa với mục đích:


Mấy trang gần đây lại có thêm các bài tranh luận của các bậc thầy nữa kia đấy!

Phát sợ & ngơ ngẩn luôn!

Cái "nữa kia đấy" nó có chút mỉa mai bạn nhỉ.

Tranh luận là tốt chứ nhỉ.
Bạn có hiểu code Nghĩa đưa ra không? Nếu bạn hiểu thì không lý gì bạn lại không hiểu code có được sau khi tranh luận. Vì cả code của Nghĩa và code chỉnh sửa sau tranh luận đều chỉ dùng những cấu trúc cơ bản của ngôn ngữ VBA, cụ thể là For ... Next và Do ... Loop. Vậy cái "Phát sợ & ngơ ngẩn luôn!" là thế nào?

Tôi cho bạn 2 code, một code có khiếm khuyết, thậm chí là sai và 1 code chuẩn thì bạn chọn code nào? Nếu cả 2 code bạn không hiểu thì không nói làm gì nhưng nếu bạn hiểu code 1 thì không lý gì bạn lại không hiểu code 2. Ngoài ra nếu code chuẩn bạn không hiểu, hay nói tổng quát nếu kiến thức chuẩn mà hiện thời bạn chưa hiểu thì bạn luôn có thể "để dành" và trong tương lai nếu cần bạn có chỗ để quay lại đọc và nghiền ngẫm. Bạn chả mất gì cả. Kiến thức chưa hiểu thì cùng lắm bạn có thể "tạm lờ" đi chứ nó không thể làm bạn hiểu sai. Nhưng kiến thức sai sẽ làm bạn hiểu sai và trong tương lai bạn sẽ đi theo con "đường mòn" đó.

Tôi có một phương châm: không giúp được gì cho người ta có nghĩa là người ta không học được thêm gì mới. Chỉ thế thôi. Nhưng cho người ta kiến thức sai là làm hại người ta. Cũng như bác sỹ ấy. Chữa được bệnh là tốt nhưng không thể chữa để người ta thêm bệnh được.

Mà bạn đọc các bài tranh luận bạn không học thêm được cái gì à? Nhờ có tranh luận mới "lộ" thêm cách thức hoạt động của FOR. Những người mới học không có được lợi gì từ sự giải thích cách thức hoạt động của FOR? Nếu thế thì bó tay rồi. Cũng là một bài giảng vd. của thầy ptm0412 nhưng rất có thể có người hiểu nhưng cũng có người không hiểu, đơn giản là vì không cố gắng hiểu. Đơn giản vì ngay từ đầu đã thấy "Phát sợ & ngơ ngẩn luôn" nên không cố, không có ý định tìm hiểu làm gì
 
Upvote 0
Một câu trong bài test cuối khóa VBA căn bản:
(Người ra đề: ptm0412)
Đề trong file word, làm bài trong file excel.

Các bạn có thể tham khảo và làm thử. Chương trình căn bản gói gọn trong đó, không có biến mảng.
 

File đính kèm

Upvote 0
Một câu trong bài test cuối khóa VBA căn bản:
(Người ra đề: ptm0412)
Đề trong file word, làm bài trong file excel.

Các bạn có thể tham khảo và làm thử. Chương trình căn bản gói gọn trong đó, không có biến mảng.
Em xin "giơ tay phát biểu"
Do dữ liệu giả định ít và kiến thức còn hạn chế nên bài làm của em mong các anh chị chỉ thêm
* Máy em định dạng số kiểu US nên dùng dấu "," ngăn cách hàng thập phân
Mã:
Sub Ending_Test()

Dim i As Long, store1 As Long, store2 As Long, store3 As Long, store4 As Long, j

Do
  j = InputBox("Ban chon loai SP nao?" & vbNewLine & "(a, b, c, d)", "chon loai")
    If j = "a" Or j = "b" Or j = "c" Or j = "d" Then
      Exit Do
    Else
      MsgBox "NHAP SAI! VUI LONG NHAP LAI!", vbCritical, "Thong Bao"
    End If
Loop

store1 = 0: store2 = 0: store3 = 0: store4 = 0 'Viết luôn thế này cho rõ

For i = 3 To 15
  If Cells(i, 1) = j Then
    With Cells(i, 1)
      store1 = store1 + .Offset(, 1)
      store2 = store2 + .Offset(, 2)
      store3 = store3 + .Offset(, 3)
      store4 = store4 + .Offset(, 4)
    End With
  End If
Next i

MsgBox "Tong doanh so mat hang " & j & vbCrLf & _
       "- Store 01 : " & Format(store1, "###,###") & vbNewLine & _
       "- Store 02 : " & Format(store2, "###,###") & Chr(10) & _
       "- Store 03 : " & Format(store3, "###,###") & vbCrLf & _
       "- Store 04 : " & Format(store4, "###,###") _
       , , "Ket qua"

End Sub
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Một câu trong bài test cuối khóa VBA căn bản:
(Người ra đề: ptm0412)
Đề trong file word, làm bài trong file excel.

Các bạn có thể tham khảo và làm thử. Chương trình căn bản gói gọn trong đó, không có biến mảng.
Em làm thử, nhờ các Thầy xem giúp:
Mã:
Private Sub CommandButton1_Click()
Dim I, A, B, C, D
Dim Kho, Rng
    Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon kho", , vbOKCancel)
    Set Rng = Sheet4.Range("A3:A16")
        For I = 1 To Rng.Rows.Count
            If Rng(I) = Kho Then
                A = A + Rng(I).Offset(, 1)
                B = B + Rng(I).Offset(, 2)
                C = C + Rng(I).Offset(, 3)
                D = D + Rng(I).Offset(, 4)
            End If
        Next I
        A = A
        B = B
        C = C
        D = D
    MsgBox "Tong doanh so mat hang " & Kho & ":" _
        & Chr(13) & "- Store 01: " & A _
        & Chr(13) & "- Store 02: " & B _
        & Chr(13) & "- Store 03: " & C _
        & Chr(13) & "- Store 04: " & D
End Sub
Em chưa định dạng được số trong MsgBox.
Sản phẩm b Store 04 là 16,000 mà Thầy cho trong hình ảnh là 8,000.
 

File đính kèm

Upvote 0
Một câu trong bài test cuối khóa VBA căn bản:
(Người ra đề: ptm0412)
Đề trong file word, làm bài trong file excel.

Các bạn có thể tham khảo và làm thử. Chương trình căn bản gói gọn trong đó, không có biến mảng.
Em cũng đang tập tọe học VBA, các thầy góp ý bài của em nhé!

[Sub baitest()
Dim store01 As Double, store02 As Double, store03 As Double, store04 As Double
Dim chon As String
Dim i As Long
chon = InputBox("Ban chon san pham nao?" & vbNewLine & "(a,b,c,d)", "chon loai")
With Sheet4
For i = 3 To .Range("A10000").End(xlUp).Row
If .Range("A" & i) = chon Then
store01 = .Range("B" & i) + store01
store02 = .Range("C" & i) + store02
store03 = .Range("D" & i) + store03
store04 = .Range("E" & i) + store04
End If
Next
MsgBox "Tong doanh so mat hang " & chon & ":" & vbNewLine & _
"- Store 01: " & Format(store01, "#,###") & vbNewLine & "- Store 02: " & Format(store02, "#,###") & _
vbNewLine & "- Store 03: " & Format(store03, "#,###") & vbNewLine & _
"- Store 04: " & Format(store04, "#,###"), vbDefaultButton1, "Ket qua"
End With
End Sub
 

File đính kèm

Upvote 0
Em làm thử, nhờ các Thầy xem giúp:
Mã:
Private Sub CommandButton1_Click()
Dim I, A, B, C, D
Dim Kho, Rng
    Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon kho", , vbOKCancel)
    Set Rng = Sheet4.Range("A3:A16")
        For I = 1 To Rng.Rows.Count
            If Rng(I) = Kho Then
                A = A + Rng(I).Offset(, 1)
                B = B + Rng(I).Offset(, 2)
                C = C + Rng(I).Offset(, 3)
                D = D + Rng(I).Offset(, 4)
            End If
        Next I
        A = A
        B = B
        C = C
        D = D
    MsgBox "Tong doanh so mat hang " & Kho & ":" _
        & Chr(13) & "- Store 01: " & A _
        & Chr(13) & "- Store 02: " & B _
        & Chr(13) & "- Store 03: " & C _
        & Chr(13) & "- Store 04: " & D
End Sub
Em chưa định dạng được số trong MsgBox.
Sản phẩm b Store 04 là 16,000 mà Thầy cho trong hình ảnh là 8,000.
Code của Leo hình như dư mấy cái thằng này
A = A
B = B
C = C
D = D
Thân
 
Upvote 0
Code của Leo hình như dư mấy cái thằng này
A = A
B = B
C = C
D = D
Thân
Không hiểu sao lúc đầu chạy thử lại thiếu mất kết quả vòng lặp cuối cùng, nên em cho thêm mấy thằng này, giờ bỏ ra lại thấy đủ ?!
Sửa lại code:
Mã:
Private Sub CommandButton1_Click()
Dim I As Long, A As Long, B As Long, C As Long, D As Long
Dim Kho As String, Rng As Range
    Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbOKCancel)
        If Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" Then
            Set Rng = Sheet4.Range("A3:A16")
                For I = 1 To Rng.Rows.Count
                    If Rng(I) = Kho Then
                        A = A + Rng(I).Offset(, 1)
                        B = B + Rng(I).Offset(, 2)
                        C = C + Rng(I).Offset(, 3)
                        D = D + Rng(I).Offset(, 4)
                    End If
                Next I
        Else
            MsgBox "Ban chua chon san pham"
            Exit Sub
        End If
    MsgBox "Tong doanh so mat hang " & Kho & ":" _
        & Chr(13) & "- Store 01: " & Format(A, "#,###") _
        & Chr(13) & "- Store 02: " & Format(B, "#,###") _
        & Chr(13) & "- Store 03: " & Format(C, "#,###") _
        & Chr(13) & "- Store 04: " & Format(D, "#,###") _
        , , "Ket qua"
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Em làm thử, nhờ các Thầy xem giúp:
Mã:
Private Sub CommandButton1_Click()
Dim I, A, B, C, D
Dim Kho, Rng
[/QUOTE]
Theo mình nên có thói quen khai báo biến tường minh ngay từ đầu để có phương pháp khai thác Vba hiệu quả, tránh lãng phí...
 
Lần chỉnh sửa cuối:
Upvote 0
Không hiểu sao lúc đầu chạy thử lại thiếu mất kết quả vòng lặp cuối cùng, nên em cho thêm mấy thằng này, giờ bỏ ra lại thấy đủ ?!
Sửa lại code:
Mã:
Private Sub CommandButton1_Click()
Dim I As Long, A As Long, B As Long, C As Long, D As Long
Dim Kho As String, Rng As Range
    Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbOKCancel)
        If Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" Then
            Set Rng = Sheet4.Range("A3:A16")
                For I = 1 To Rng.Rows.Count
                    If Rng(I) = Kho Then
                        A = A + Rng(I).Offset(, 1)
                        B = B + Rng(I).Offset(, 2)
                        C = C + Rng(I).Offset(, 3)
                        D = D + Rng(I).Offset(, 4)
                    End If
                Next I
        Else
            MsgBox "Ban chua chon san pham"
            Exit Sub
        End If
    MsgBox "Tong doanh so mat hang " & Kho & ":" _
        & Chr(13) & "- Store 01: " & Format(A, "#,###") _
        & Chr(13) & "- Store 02: " & Format(B, "#,###") _
        & Chr(13) & "- Store 03: " & Format(C, "#,###") _
        & Chr(13) & "- Store 04: " & Format(D, "#,###") _
        , , "Ket qua"
End Sub

Code của bạn có 1 vấn đề: khi hiện inputbox() nếu ng dùng gõ ko đúng sản phẩm hoặc bấm cancel thì code vẫn hiện lên msgbox "tong doanh so mat hang .... "? Ko ổn... nên đưa msgbox đấy vào trong if
 
Lần chỉnh sửa cuối:
Upvote 0
Code của bạn có 1 vấn đề: nếu khi hiện inputbox() nếu ng dùng gõ ko đúng sản phẩm hoặc bấm cancel thì code vẫn hiện lên msgbox "tong doanh so mat hang .... "? Ko ổn... nên đưa msgbox đấy vào trong if
Đã Exit Sub rồi mà, làm gì có hiện lên MsgBox "Tổng doanh số ..."?
 
Upvote 0
Bài này nếu tốt thì mình nên đặt input ... Ngay trong do.. Loop thì hay hơn. Để chắc chắn người nhập nhập đúng đk rồi mớic thực hiện các lệnh khác
 
Upvote 0
Code của bạn có 1 vấn đề: khi hiện inputbox() nếu ng dùng gõ ko đúng sản phẩm hoặc bấm cancel thì code vẫn hiện lên msgbox "tong doanh so mat hang .... "? Ko ổn... nên đưa msgbox đấy vào trong if

Theo bạn thì bạn phải viết như thế nào mới đúng, hãy tham gia giải đáp bài tập đi bạn.
 
Upvote 0
Nếu các bạn đưa 1 message thông báo chọn sai loại sản phẩm, nên test các trường hợp:

1. Trong Inputbox chọn loại:

- Nhấn OK
- Nhấn Cancel

Mỗi trường hợp nhấn trên phải test 2 trường hợp:
- Gõ đúng tên Sp
- Gõ sai tên SP hoặc bỏ trống

2. Trong Msgbox thông báo sai:
- Nhấn Yes/OK
- Nhấn No/Cancel

Nên dùng vbOKCancel hoặc vbYesNo, không dùng vbOKOnly, vì bất kỳ lúc nào người dùng cũng có thể muốn ngưng. Không nên hành hạ người dùng làm đến khi đúng mới thoát được nợ.
 
Upvote 0
* Máy em định dạng số kiểu US nên dùng dấu "," ngăn cách hàng thập phân

Máy định dạng kiểu nào trong hệ thống thì trong VBA cứ định dạng "#,##0" cũng sẽ ra đúng định dạng của hệ thống.

Máy ở nhà tôi (trong hình chụp) định dạng kiểu Pháp, nhưng trong VBA tôi vẫn viết "#,##0", và kết quả như hình chụp.
 
Upvote 0
Bài này nếu tốt thì mình nên đặt input ... Ngay trong do.. Loop thì hay hơn. Để chắc chắn người nhập nhập đúng đk rồi mớic thực hiện các lệnh khác
Thử viết code theo cách của bạn Phi xem sao! Nghe cũng khá lạ đấy!
 
Upvote 0
Đã Exit Sub rồi mà, làm gì có hiện lên MsgBox "Tổng doanh số ..."?

Theo tôi không nên dùng Exit sub ngay lập tức kiểu đó. Con người có lúc gõ nhầm, cứ Exit ngay thì người ta lại phải nhấn nút "chạy" code từ đầu? Nên cho người ta cơ hội sửa lại. Vì thế tôi thấy bài #127 dùng Do ... Loop là được. Nhưng code còn "thiếu". Với code hiện thời người dùng không "thôi chơi" bằng cách nhấn Cancel được vì vòng lặp cứ hiển thị InputBox liên tục. Không phải vô cớ mà Inputbox có nút Cancel. Nếu Inputbox có nút Cancel thì cũng có nghĩa là cho phép người dùng "thôi chơi". Vậy ta nhập vào Inputbox giá trị mặc định nào đó, vd. "a". Khi Inputbox trở về thì ta kiểm tra giá trị. Nếu = "" tức người dùng nhấn Cancel - "thôi chơi" thì ta dọn "đồ chơi". Nếu <> "" thì ta kiểnm tra có là "a", ..., "d" hay không ...
 
Upvote 0
Theo bạn thì bạn phải viết như thế nào mới đúng, hãy tham gia giải đáp bài tập đi bạn.
Em đã tham gia ở bài #127
và theo gợi ý của bác PTM0412 e xin sửa 1 tí
Mã:
Sub Ending_Test()

Dim i As Long, store1 As Long, store2 As Long, store3 As Long, store4 As Long, j, k

Do
  j = InputBox("Ban chon loai SP nao?" & vbNewLine & "(a, b, c, d)", "chon loai")
    If j = "a" Or j = "b" Or j = "c" Or j = "d" Then
      Exit Do
    Else
      [COLOR=#0000cd]If j = "" Then Exit Sub              'same: If j = vbNullString Then ...[/COLOR]
      k = MsgBox("NHAP SAI! VUI LONG NHAP LAI! Chon Cancel neu muon thoat", vbOKCancel, "Thong Bao")
      [COLOR=#0000cd]If k = vbCancel Then Exit Sub[/COLOR]
    End If
Loop

store1 = 0: store2 = 0: store3 = 0: store4 = 0 [COLOR=#0000cd]'Viết thế này luôn cho rõ[/COLOR]
For i = 3 To 15
  If Cells(i, 1) = j Then
    With Cells(i, 1)
      store1 = store1 + .Offset(, 1)
      store2 = store2 + .Offset(, 2)
      store3 = store3 + .Offset(, 3)
      store4 = store4 + .Offset(, 4)
    End With
  End If
Next i

MsgBox "Tong doanh so mat hang " & j & vbCrLf & _
       "- Store 01 : " & Format(store1, "#,###") & vbNewLine & _
       "- Store 02 : " & Format(store2, "#,###") & [COLOR=#ff0000]Chr(10)[/COLOR] & _
       "- Store 03 : " & Format(store3, "#,###") & [COLOR=#ff0000]Chr(13)[/COLOR] & _
       "- Store 04 : " & Format(store4, "#,###") _
       , , "Ket qua"

End Sub
Cho em hỏi luôn là Chr(10) & Chr(13) đều là ngắt xuống dòng, là một hay có gì khác nhau? Xin vui lòng giải thích
 
Lần chỉnh sửa cuối:
Upvote 0
store1 = 0: store2 = 0: store3 = 0: store4 = 0 'Viết thế này luôn cho rõ

Thay vì cho nó rõ thì nó lại là thừa nha bạn, bởi mặc định của các biến số bao giờ cũng là 0 hết nhé!


Cho em hỏi luôn là Chr(10) & Chr(13) đều là ngắt xuống dòng, là một hay có gì khác nhau? Xin vui lòng giải thích

Các bạn hãy xem bảng này nhé! Theo tôi, tốt nhất khi xuống hàng thì ta dùng vbLf cho dễ nhớ, lại ngắn gọn! Xem hình.
 

File đính kèm

  • MisConst.jpg
    MisConst.jpg
    142.2 KB · Đọc: 83
Lần chỉnh sửa cuối:
Upvote 0

Thay vì cho nó rõ thì nó lại là thừa nha bạn, bởi mặc định của các biến số bao giờ cũng là 0 hết nhé!
Cần phải phát biểu chính xác và rõ ràng như sau:

- Biến khai báo kiểu Number (Long, Integer, Double, ...) sẽ có giá trị ban đầu là 0
- Biến khai báo kiểu String sẽ có giá trị ban đầu là chuỗi rỗng ("", vbNullString)
- Biến khai báo kiểu Object (Range, Sheet, ...) sẽ có giá trị ban đầu là Nothing
- Biến mảng không khai báo kiểu, có giá trị là Empty
 
Upvote 0
Cần phải phát biểu chính xác và rõ ràng như sau:


- Biến mảng không khai báo kiểu, có giá trị là Empty

Xin Sư phụ vui lòng xác nhận lại "Biến mảng" (Array) hay biến Variant?

Bởi: Dim Arr Dim Arr() là hoàn toàn khác nhau!
 
Lần chỉnh sửa cuối:
Upvote 0
Xin Sư phụ vui lòng xác nhận lại "Biến mảng" (Array) hay biến Variant?

Bởi: Dim Arr Dim Arr() là hoàn toàn khác nhau!

Thông thường câu tôi viết trong chuyên đề nghiêm túc thường không dư không thiếu.

Tôi viết là Biến mảng không khai báo kiểu

Nghĩa là Dim Arr() hoặc Dim/ReDim Arr(1 to 10, 1 to 3)

không As iếc



Nói thêm:
Các phát biểu của tôi cũng có thể sai (đương nhiên). Nếu Nghĩa hoặc bất kỳ ai thấy sai thì vạch ngay ra chỗ sai, đừng ngại, và cũng đừng yêu cầu tôi xác nhận lại.
 
Lần chỉnh sửa cuối:
Upvote 0
Thử viết code theo cách của bạn Phi xem sao! Nghe cũng khá lạ đấy!
Private Sub CommandButton1_Click()
Dim I As Long, A As Long, B As Long, C As Long, D As Long
Dim Kho As String, Rng As Range
Do
Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbOKCancel)
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d")
Set Rng = Sheet4.Range("A3:A16")
For I = 1 To Rng.Rows.Count
If Rng(I) = Kho Then
A = A + Rng(I).Offset(, 1)
B = B + Rng(I).Offset(, 2)
C = C + Rng(I).Offset(, 3)
D = D + Rng(I).Offset(, 4)
End If
Next I

MsgBox "Tong doanh so mat hang " & Kho & ":" _
& Chr(13) & "- Store 01: " & Format(A, "#,###") _
& Chr(13) & "- Store 02: " & Format(B, "#,###") _
& Chr(13) & "- Store 03: " & Format(C, "#,###") _
& Chr(13) & "- Store 04: " & Format(D, "#,###") _
, , "Ket qua"
End Sub
cái này sửa lại code của leonguyen 1 tí,
nếu mình không gõ đúng các ký tự a,b,c,d thì nó bắt mình nhập cho tới khi nào đúng thì ok. mình không phải kiểm tra vì giá trị nhập phải luôn luôn đúng
 
Upvote 0
Mã:
Option Explicit
[B]Sub BaiTapTH()[/B]
 Dim Cls As Range, Rng As Range, tRng As Range
 Dim LoaiSP As String, Addr As String
 
 Dim Arr(1 To 4) As Double:         Dim Jj As Long
 
 LoaiSP = "Hay Nhap Loai SP ma Ban Can Tinh"
 LoaiSP = InputBox(LoaiSP, "GPE Xin Chao Cac Ban", "a")
 If InStr("abcd", LoaiSP) Then
    Set Rng = Range([A2], [A2].End(xlDown))
    Set tRng = Rng.Find(LoaiSP, , xlFormulas, xlWhole)
    If Not tRng Is Nothing Then
        Addr = tRng.Address
        Do
            With tRng
                For Jj = 1 To 4
                    Arr(Jj) = Arr(Jj) + .Offset(, Jj).Value
                Next Jj
            End With
            Set tRng = Rng.FindNext(tRng)
        Loop While Not tRng Is Nothing And tRng.Address <> Addr
    End If
    MsgBox "Tong Doanh So Mat Hang " & LoaiSP & ":" _
        & Chr(10) & "- Store 01:" & Str((Arr(1) \ 10 ^ 3)) & ".000" _
        & Chr(10) & Arr(2) & Chr(10) & Arr(3) & Chr(10) & Arr(4)
 Else
    MsgBox "Chuc Ban May Man!"
 End If
[B]End Sub[/B]
 
Upvote 0
nếu mình không gõ đúng các ký tự a,b,c,d thì nó bắt mình nhập cho tới khi nào đúng thì ok. mình không phải kiểm tra vì giá trị nhập phải luôn luôn đúng
Vậy thì góp ý của bác Ptm0412 (#138) và Siwtom (#141) coi như là lãng phí rồi. người dùng ko muốn chơi với Inputbox nữa thì cũng "đâu cái điền". Hic hic ...
 
Upvote 0
Thông thường câu tôi viết trong chuyên đề nghiêm túc thường không dư không thiếu.

Tôi viết là Biến mảng không khai báo kiểu

Nghĩa là Dim Arr() hoặc Dim/ReDim Arr(1 to 10, 1 to 3)

không As iếc



Nói thêm:
Các phát biểu của tôi cũng có thể sai (đương nhiên). Nếu Nghĩa hoặc bất kỳ ai thấy sai thì vạch ngay ra chỗ sai, đừng ngại, và cũng đừng yêu cầu tôi xác nhận lại.

Nếu Sư phụ nói Arr() là biến mảng, và cũng nói nếu không khai báo kiểu thì có giá trị Empty là không chính xác!

Xin kiểm chứng cho Code này:

Mã:
Sub test()
    Dim sArr
    Dim Arr()
    MsgBox IsEmpty(sArr)
    MsgBox IsEmpty(Arr)
End Sub

Như vậy, biến sArr không khai báo, mặc nhiên nó là một biến Variant, thì nó mới chính là có giá trị Empty!
 
Upvote 0
Với bài của Sư phụ ra đề, rất thiết thực và rất vừa tầm đối với các thành viên mới (mới có nghĩa là mới học VBA).

Qua các thành viên gửi bài lên tham gia, tôi nhận thấy mỗi người có một thuật toán, một cách đặt biến, một cách trình bày cũng có ít nhiều khác nhau.

Tuy vậy, có điểm chung là tương đối đúng với yêu cầu của đề bài đưa ra. Qua đó, là một người ít nhiều gì cũng tự lăn lộn để học code như các bạn, tôi có một số kinh nghiệm để nói về bài này như sau:

1) Cách khai báo biến các bạn nên tạo ngắn gọn và gợi nhớ, ví như bài của leonguyenz, đã nhập giá trị SP là a, b, c, d, lại đi khai báo biến A, B, C, D như thế dễ khiến cho người đọc code lẫn lộn Store với SP. Vì thế ta có thể khai Store1, Store4... như một số bạn đã làm.

2) Chúng ta chăm chút việc bẫy lỗi a, b, c, d bằng nhiều phương pháp, nhưng chúng ta chưa để ý đến việc có thể người ta nhập là A, B, C, D. Lẽ ra khi bẫy lỗi chúng ta hoặc dùng LCase, hoặc dùng UCase cho từ 2 phía, chẳng hạn: If UCase(Rng(i)) = UCase(Kho) Then thì sẽ không bị mắc sai lầm.

Tôi nghĩ còn nhiều nhận xét khác, nhưng với đây là bài của Sư phụ đưa ra, vậy xin "múa rìu qua mắt thợ" được 2 nhận xét trên, xin nhường quyền nhận xét lại cho Sư phụ và các cao thủ khác.
 
Lần chỉnh sửa cuối:
Upvote 0
Mượn tạm bài của bạn phanminhphuong để chế biến 1 cách để đạt được yêu cầu của Ptm0412
dùng WorksheetFuntion trong bài cơ bản này mong mọi người đừng cười, cũng là 1 cách đạt mục tiêu thôi
Mã:
Sub Ending_Test1()

Dim i As Long, j, k
Dim store1 As Long, store2 As Long, store3 As Long, store4 As Long
Dim myRange As Range

Do
  j = InputBox("Ban chon loai SP nao?" & vbNewLine & "(a, b, c, d)", "chon loai")
    If UCase(j) = Ucase("a") Or UCase(j) = UCase("b") Or UCase(j) = UCase("c") Or UCase(j) = UCase("d") Then
      Exit Do
    Else
      If j = "" Then [COLOR=#0000cd]Exit Sub[/COLOR]
        k = MsgBox("NHAP SAI! VUI LONG NHAP LAI!", vbOKCancel, vbCritical, "Thong Bao")
        If k = vbCancel Then [COLOR=#0000cd]Exit Sub[/COLOR]
    End If
Loop

Set myRange = Range("A3:A16")
store1 = [COLOR=#0000cd]Application.WorksheetFunction[/COLOR].SumIf(myRange, j, Range("B3:B16"))
store2 = [COLOR=#0000cd]Application.WorksheetFunction[/COLOR].SumIf(myRange, j, Range("C3:C16"))
store3 = [COLOR=#0000cd]Application.WorksheetFunction[/COLOR].SumIf(myRange, j, Range("D3:D16"))
store4 = [COLOR=#0000cd]Application.WorksheetFunction[/COLOR].SumIf(myRange, j, Range("E3:E16"))

MsgBox "Tong doanh so mat hang " & j & vbCrLf & _
       "- Store 01 : " & Format(store1, "###,###") & vbNewLine & _
       "- Store 02 : " & Format(store2, "###,###") & Chr(10) & _
       "- Store 03 : " & Format(store3, "###,###") & vbCrLf & _
       "- Store 04 : " & Format(store4, "###,###") _
       , , "Ket qua"

End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Mã:
Option Explicit
[B]Sub BaiTapTH()[/B]
 Dim Cls As Range, Rng As Range, tRng As Range
 Dim LoaiSP As String, Addr As String
 
 Dim Arr(1 To 4) As Double:         Dim Jj As Long
 
 LoaiSP = "Hay Nhap Loai SP ma Ban Can Tinh"
 LoaiSP = InputBox(LoaiSP, "GPE Xin Chao Cac Ban", "a")
 [B][COLOR=#ff0000]If InStr("abcd", LoaiSP) Then[/COLOR][/B]
    Set Rng = Range([A2], [A2].End(xlDown))
    Set tRng = Rng.Find(LoaiSP, , xlFormulas, xlWhole)
    If Not tRng Is Nothing Then
        Addr = tRng.Address
        Do
            With tRng
                For Jj = 1 To 4
                    Arr(Jj) = Arr(Jj) + .Offset(, Jj).Value
                Next Jj
            End With
            Set tRng = Rng.FindNext(tRng)
        Loop While Not tRng Is Nothing And tRng.Address <> Addr
    End If
    MsgBox "Tong Doanh So Mat Hang " & LoaiSP & ":" _
        & Chr(10) & "- Store 01:" & Str((Arr(1) \ 10 ^ 3)) & ".000" _
        & Chr(10) & Arr(2) & Chr(10) & Arr(3) & Chr(10) & Arr(4)
 Else
    MsgBox "Chuc Ban May Man!"
 End If
[B]End Sub[/B]

Người dùng mà nhấn Cancel thì code vẫn tính toán. Đúng là code "cứng đầu".
Ngoài ra có thể nhập ab, abc, abcd, bc, bcd, cd thì code vẫn tính. Code dễ dãi quá
 
Upvote 0
Do
Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbOKCancel)
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d")


Sau khi bấm nút lệnh để hiển thị InputBox, người dùng nghĩ lại, "thôi không nhập, thoát đi", thế là bấm Cancel, chuyện gì xảy ra với code của bạn vậy? Bắt buộc người ta phải nhập và tính toán hay sao?

Cái mà tôi tô màu đỏ, mặc dù tôi biết bạn mượn code, nhưng cũng nên kiểm tra lại xem có hợp lý không hen!
 
Upvote 0
Mượn tạm bài của bạn phanminhphuong để chế biến 1 cách để đạt được yêu cầu của Ptm0412
dùng WorksheetFuntion trong bài cơ bản này mong mọi người đừng cười, cũng là 1 cách đạt mục tiêu thôi
Mã:
    If UCase(j) = Ucase("a") Or UCase(j) = UCase("b") Or UCase(j) = UCase("c") Or UCase(j) = UCase("d")

Thay vì như thế, sao không viết như thế này để nó bớt tính toán nhỉ?

If UCase(j) = "A" Or UCase(j) = "B" Or UCase(j) = "C" Or UCase(j) = "D"
 
Upvote 0
Sau khi bấm nút lệnh để hiển thị InputBox, người dùng nghĩ lại, "thôi không nhập, thoát đi", thế là bấm Cancel, chuyện gì xảy ra với code của bạn vậy? Bắt buộc người ta phải nhập và tính toán hay sao?

Cái mà tôi tô màu đỏ, mặc dù tôi biết bạn mượn code, nhưng cũng nên kiểm tra lại xem có hợp lý không hen!
\
vì cái bài này khi hiện ra thông báo có gợi ý phải nhập 1 trong 4 giá trị. thì theo như code là buộc phải nhập 1 trong 4 giá trị ( chưa bẫy lỗi chữ hoa chữ thường)

nếu người dùng không muốn tính toán mà muốn thoát thì mình bẫy lỗi thêm
Do
Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbYesNoCancel)
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" Or Kho = vbCanel)
Set Rng = Sheet4.Range("A3:A16")
If (Kho <> vbCanel) Then
......
end if
 
Upvote 0
Mượn tạm bài của bạn phanminhphuong để chế biến 1 cách để đạt được yêu cầu của Ptm0412
dùng WorksheetFuntion trong bài cơ bản này mong mọi người đừng cười, cũng là 1 cách đạt mục tiêu thôi
Mã:
Sub Ending_Test1()

Dim i As Long, j, k
Dim store1 As Long, store2 As Long, store3 As Long, store4 As Long
Dim myRange As Range

Do
  j = InputBox("Ban chon loai SP nao?" & vbNewLine & "(a, b, c, d)", "chon loai")
    If UCase(j) = Ucase("a") Or UCase(j) = UCase("b") Or UCase(j) = UCase("c") Or UCase(j) = UCase("d") Then
      Exit Do
    Else
      If j = "" Then [COLOR=#0000cd]Exit Sub[/COLOR]
        k = MsgBox("NHAP SAI! VUI LONG NHAP LAI!", vbOKCancel, vbCritical, "Thong Bao")
        If k = vbCancel Then [COLOR=#0000cd]Exit Sub[/COLOR]
    End If
Loop

Set myRange = Range("A3:A16")
store1 = [COLOR=#0000cd]Application.WorksheetFunction[/COLOR].SumIf(myRange, j, Range("B3:B16"))
store2 = [COLOR=#0000cd]Application.WorksheetFunction[/COLOR].SumIf(myRange, j, Range("C3:C16"))
store3 = [COLOR=#0000cd]Application.WorksheetFunction[/COLOR].SumIf(myRange, j, Range("D3:D16"))
store4 = [COLOR=#0000cd]Application.WorksheetFunction[/COLOR].SumIf(myRange, j, Range("E3:E16"))

MsgBox "Tong doanh so mat hang " & j & vbCrLf & _
       "- Store 01 : " & Format(store1, "###,###") & vbNewLine & _
       "- Store 02 : " & Format(store2, "###,###") & Chr(10) & _
       "- Store 03 : " & Format(store3, "###,###") & vbCrLf & _
       "- Store 04 : " & Format(store4, "###,###") _
       , , "Ket qua"

End Sub
KHÔNG CÓ GÌ MÀ PHẢI SỢ NGƯỜI TA CƯỜI. CỨ VIẾT THEO MÌNH HIỂU. TÔI CŨNG ĐANG HỌC BẠN ĐẤY THÔI.
HÌNH NHƯ CODE CỦA BẠN BIẾN i TÔI CHẢ THẤY BẠN SỬ DỤNG CHỖ NÀO
 
Upvote 0
Phải vậy không ta bạn hiền
PHP:
Private Sub CommandButton1_Click()
Dim I As Long, store1 As Long, store2 As Long, store3 As Long, store4 As Long
Dim Kho As String, Rng As Range
Do
Kho = LCase(InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbOKCancel))
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" Or Kho = "")
Set Rng = Sheet4.Range("A3:A16")
If (Kho = "") Then
Exit Sub
Else
For I = 1 To Rng.Rows.Count
If Rng(I) = Kho Then
store1 = store1 + Rng(I).Offset(, 1)
store2 = store2 + Rng(I).Offset(, 2)
store3 = store3 + Rng(I).Offset(, 3)
store4 = store4 + Rng(I).Offset(, 4)
End If
Next I
End If
MsgBox "Tong doanh so mat hang " & Kho & ":" _
& Chr(13) & "- Store 01: " & Format(store1, "#,###") _
& Chr(13) & "- Store 02: " & Format(store2, "#,###") _
& Chr(13) & "- Store 03: " & Format(store3, "#,###") _
& Chr(13) & "- Store 04: " & Format(store4, "#,###") _
, , "Ket qua"
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
phải vậy không ta bạn hiền
PHP:
Do
Kho = LCase(InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbYesNoCancel))
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" Or Kho = vbCanel)

Sorry bạn hiền và các "tềnh iêu", tôi lại nhầm với MsgBox, lẽ ra phải như thế này mới đúng:

Mã:
Do
    [COLOR=#006400]'InputBox([/COLOR][COLOR=#ff0000]prompt[/COLOR][COLOR=#800080][, title][/COLOR][COLOR=#006400] [, default] [, xpos] [, ypos] [, helpfile, context])[/COLOR]
    Kho = InputBox([COLOR=#ff0000]"Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)"[/COLOR], [COLOR=#800080]"Chon loai"[/COLOR])
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" [B][COLOR=#0000ff]Or Kho = ""[/COLOR][/B])
If Kho = "" Then Exit Sub

Tôi thì không khoái cho lắm loại bẫy lỗi dùng vòng lặp này, nếu là tôi thì tôi sẽ không dùng vòng lặp vẫn có thể thông báo thêm 1 tin nhắn.
 
Lần chỉnh sửa cuối:
Upvote 0
phải vậy không ta bạn hiền
PHP:
Private Sub CommandButton1_Click()
Dim I As Long, store1 As Long, store2 As Long, store3 As Long, store4 As Long
Dim Kho As String, Rng As Range
Do
Kho = LCase(InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbOKCancel))
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" Or Kho = " ")
Set Rng = Sheet4.Range("A3:A16")
If (Kho =" ") Then
Exit Sub
Else
For I = 1 To Rng.Rows.Count
If Rng(I) = Kho Then
store1 = store1 + Rng(I).Offset(, 1)
store2 = store2 + Rng(I).Offset(, 2)
store3 = store3 + Rng(I).Offset(, 3)
store4 = store4 + Rng(I).Offset(, 4)
End If
Next I
End If
MsgBox "Tong doanh so mat hang " & Kho & ":" _
& Chr(13) & "- Store 01: " & Format(store1, "#,###") _
& Chr(13) & "- Store 02: " & Format(store2, "#,###") _
& Chr(13) & "- Store 03: " & Format(store3, "#,###") _
& Chr(13) & "- Store 04: " & Format(store4, "#,###") _
, , "Ket qua"
End Sub
hiện tại có 4 cột thì dim 4 cái store. vậy có 10 cột chắc lại phải viết khác rồi đúng không thầy. lúc đó lại store &n phải không ạ
 
Lần chỉnh sửa cuối:
Upvote 0
Thay vì như thế, sao không viết như thế này để nó bớt tính toán nhỉ?

If UCase(j) = "A" Or UCase(j) = "B" Or UCase(j) = "C" Or UCase(j) = "D"
Vì mình định viết theo dạng tổng quát mà, tên Sp đâu chỉ có 1 chữ (trong bài này là vd thế thôi)
Bài khác mà Vd Range("A3")= "Main Engine" chẳng nhẽ viết UCase(j) = "MAIN ENGINE", viết thế này thôi Ucase(j) = Ucase(Range("A3"))


KHÔNG CÓ GÌ MÀ PHẢI SỢ NGƯỜI TA CƯỜI. CỨ VIẾT THEO MÌNH HIỂU. TÔI CŨNG ĐANG HỌC BẠN ĐẤY THÔI.
HÌNH NHƯ CODE CỦA BẠN BIẾN i TÔI CHẢ THẤY BẠN SỬ DỤNG CHỖ NÀO
Sorry vì copy code của phanminhphuong quên ko bỏ biến i vì không sử dụng vòng lặp.
có ai thử sử dụng code tạo PivotTable rồi lấy kết quả cho bài này ko? Cho mình tham khảo tí
 
Lần chỉnh sửa cuối:
Upvote 0
Vì mình định viết theo dạng tổng quát mà, tên Sp đâu chỉ có 1 chữ (trong bài này là vd thế thôi)
Bài khác mà Vd Range("A3")= "Main Engine" chẳng nhẽ viết UCase(j) = "MAIN ENGINE", viết thế này thôi Ucase(j) = Ucase(Range("A3"))
Đúng thế, nhưng tùy trường hợp mà áp dụng cụ thể bạn hen!

hiện tại có 4 cột thì dim 4 cái store. vậy có 10 cột chắc lại phải viết khác rồi đúng không thầy. lúc đó lại store &n phải không ạ

"Được voi là đòi hai bà Trưng" liền à! Dĩ nhiên sẽ có nhiều phương pháp để tính, nhưng hiện tại, chỉ nên biết vậy thôi hen!
 
Upvote 0
Đúng thế, nhưng tùy trường hợp mà áp dụng cụ thể bạn hen!



"Được voi là đòi hai bà Trưng" liền à! Dĩ nhiên sẽ có nhiều phương pháp để tính, nhưng hiện tại, chỉ nên biết vậy thôi hen!
tạm thời lấy cái chân voi cái đã. còn 3 chân kia khi nào lấy được rồi sẽ tìm bà trưng sau
PHP:
Private Sub CommandButton1_Click()
    Dim store1 As Long, store2 As Long, store3 As Long, store4 As Long, I As Long
    Dim Kho As String, Rng As Range
ComeBack:
    Kho = LCase(InputBox("Ban chon loai san pham nao?" & vbLf & "(a,b,c,d)", "Chon loai"))
     Select Case Kho
    Case "a", "b", "c", "d"
        Set Rng = Sheet4.Range("A3:A16")
        For I = 1 To Rng.Rows.Count
            If LCase(Rng(I)) = Kho Then
                store1 = store1 + Rng(I, 2)
                store2 = store2 + Rng(I, 3)
                store3 = store3 + Rng(I, 4)
                store4 = store4 + Rng(I, 5)
            End If
        Next I
        MsgBox "Tong doanh so mat hang " & Kho & ":" _
                & vbLf & "- Store 01: " & Format(store1, "#,###") _
                & vbLf & "- Store 02: " & Format(store2, "#,###") _
                & vbLf & "- Store 03: " & Format(store3, "#,###") _
                & vbLf & "- Store 04: " & Format(store4, "#,###") _
                , , "Ket qua"
    Case Else
        If MsgBox("Ban chon khong dung hoac khong chon ten san pham nao!" & vbLf & _
        "Ban co muon chon lai khong?", vbYesNo + vbQuestion, "Thông báo") = vbYes Then
            GoTo ComeBack
        End If
    End Select
End Sub
 
Upvote 0
Nếu Sư phụ nói Arr() là biến mảng, và cũng nói nếu không khai báo kiểu thì có giá trị Empty là không chính xác!
Xin kiểm chứng cho Code này:
Mã:
Sub test()
    Dim sArr
    Dim Arr()
    MsgBox IsEmpty(sArr)
    MsgBox IsEmpty(Arr)
End Sub
Như vậy, biến sArr không khai báo, mặc nhiên nó là một biến Variant, thì nó mới chính là có giá trị Empty!

Nghĩa đang nhầm lẫn 2 việc:
1. Tôi không nói đến biến variant. Sarr hay Staolao gì, nếu không khai báo kiểu thì nó là variant là đương nhiên.
2. Tôi nói giá trị của biến, chứ tôi không nói isEmty của biến

Ngoài ra:

3. Code do chính Nghĩa viết, vậy Nghĩa đã chạy chưa? Dẹp sArr và STaolao qua 1 bên, MsgBox IsEmpty(Arr) cho giá trị True hay False? Nếu False thì Arr có Empty không? (Câu này sai)

Tôi phải nói thêm:

1. Một biến mảng không có giá trị riêng, nếu dùng MsgBox Arr thì sẽ báo lỗi.
2. Giá trị của mảng là tập hợp các giá trị của các phần tử của nó
3. Chỉ các phần tử của mảng mới có giá trị riêng. Do đó có thể MsgBox Arr(1)
4. Kể cả khi biến mảng đã được gán giá trị cho tất cả các phần tử, IsEmpty của nó vẫn False, tức là nó vẫn Empty:

Thí dụ:

Mã:
Sub abc()
  Dim Arr(1 To 3)
   Arr(1) = 1
   Arr(2) = 2
   Arr(3) = 3
MsgBox IsEmpty(Arr) 'Ket qua False
End Sub
(Mục số 4 sai)

Còn giá trị ban đầu của biến mảng có phải Empty không, thì hãy xem hình. Ghi chú là xem lại (1) và (2): Giá trị của mảng là tập hợp các giá trị các phần tử của mảng
Hình này chứng tỏ là giá trị của phần tử của mảng Arr(2) khi chưa gán gì là Empty.

 
Lần chỉnh sửa cuối:
Upvote 0
tạm thời lấy cái chân voi cái đã. còn 3 chân kia khi nào lấy được rồi sẽ tìm bà trưng sau
PHP:
Private Sub CommandButton1_Click()
    Dim store1 As Long, store2 As Long, store3 As Long, store4 As Long, I As Long
    Dim Kho As String, Rng As Range
ComeBack:
    Kho = LCase(InputBox("Ban chon loai san pham nao?" & vbLf & "(a,b,c,d)", "Chon loai"))
     Select Case Kho
    Case "a", "b", "c", "d"
        Set Rng = Sheet4.Range("A3:A16")
        For I = 1 To Rng.Rows.Count
            If LCase(Rng(I)) = Kho Then
                store1 = store1 + Rng(I, 2)
                store2 = store2 + Rng(I, 3)
                store3 = store3 + Rng(I, 4)
                store4 = store4 + Rng(I, 5)
            End If
        Next I
        MsgBox "Tong doanh so mat hang " & Kho & ":" _
                & vbLf & "- Store 01: " & Format(store1, "#,###") _
                & vbLf & "- Store 02: " & Format(store2, "#,###") _
                & vbLf & "- Store 03: " & Format(store3, "#,###") _
                & vbLf & "- Store 04: " & Format(store4, "#,###") _
                , , "Ket qua"
    Case Else
        If MsgBox("Ban chon khong dung hoac khong chon ten san pham nao!" & vbLf & _
        "Ban co muon chon lai khong?", vbYesNo + vbQuestion, "Thông báo") = vbYes Then
            GoTo ComeBack
        End If
    End Select
End Sub


Bạn hiền tuyệt lắm! Rất hợp với ý mình! Không cần dùng vòng lặp Do ... Loop để bẫy lỗi là như thế! Great!

Dùng Select Case trong bài toán này là rất chuẩn!
 
Lần chỉnh sửa cuối:
Upvote 0
Nghĩa đang nhầm lẫn 2 việc:
1. Tôi không nói đến biến variant. Sarr hay Staolao gì, nếu không khai báo kiểu thì nó là variant là đương nhiên.
2. Tôi nói giá trị của biến, chứ tôi không nói isEmty của biến

Ngoài ra:

3. Code do chính Nghĩa viết, vậy Nghĩa đã chạy chưa? Dẹp sArr và STaolao qua 1 bên, MsgBox IsEmpty(Arr) cho giá trị True hay False? Nếu False thì Arr có Empty không?

Tôi phải nói thêm:

1. Một biến mảng không có giá trị riêng, nếu dùng MsgBox Arr thì sẽ báo lỗi.
2. Giá trị của mảng là tập hợp các giá trị của các phần tử của nó
3. Chỉ các phần tử của mảng mới có giá trị riêng. Do đó có thể MsgBox Arr(1)
4. Kể cả khi biến mảng đã được gán giá trị cho tất cả các phần tử, IsEmpty của nó vẫn False, tức là nó vẫn Empty:

Thí dụ:

Mã:
Sub abc()
  Dim Arr(1 To 3)
   Arr(1) = 1
   Arr(2) = 2
   Arr(3) = 3
MsgBox IsEmpty(Arr) 'Ket qua False
End Sub

Còn giá trị ban đầu của biến mảng có phải Empty không, thì hãy xem hình. Ghi chú là xem lại (1) và (2): Giá trị của mảng là tập hợp các giá trị các phần tử của mảng
Hình này chứng tỏ là giá trị của phần tử của mảng Arr(2) khi chưa gán gì là Empty.



IsArray(Biến) = True thì nó chính là Array, IsNumeric(Biến) = True thì nó chính là dạng Number, cũng vậy, IsEmpty(Biến) = True, thì nó chính là Empty chứ sao Sư phụ hỏi lạ vậy?**~**

Mà sao Sư phụ có thể làm Sub đó phát sinh ra lỗi cũng đặc biệt thật luôn! Em thử hoài mà nó không ra lỗi!

Mã:
Sub KhongHieu()
    Dim Arr(3)
    Arr(0) = 1
    Arr(1) = 2
    Arr(2) = 3
    Arr(3) = 4
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
hiện tại có 4 cột thì dim 4 cái store. vậy có 10 cột chắc lại phải viết khác rồi đúng không thầy. lúc đó lại store &n phải không ạ

Lúc đó dùng biến mảng Store(1 to 10)
Sau đó dùng vòng lặp con bên trong vòng For ngoài:

PHP:
For j = 1 to 10
Store(j) = Store(j) + Rng(I).Offset(, j)
Next
 
Lần chỉnh sửa cuối:
Upvote 0
IsArray(Biến) = True thì nó chính là Array, IsNumeric(Biến) = True thì nó chính là dạng Number, cũng vậy, IsEmpty(Biến) = True, thì nó chính là Empty chứ sao Sư phụ hỏi lạ vậy?**~**

Chỗ này tôi nhầm.
IsEmty(Arr) = False nghĩa là Arr() không phải Empty.

Tuy nhiên điều này có nghĩa là nó có chứa phần tử bên trong nó chứ không phải "giá trị của nó không phải empty". Còn giá trị các phần tử vẫn là Empty như bài trên tôi có thí dụ và chụp hình.
 
Upvote 0
Upvote 0
Upvote 0
Nếu Sư phụ nói Arr() là biến mảng, và cũng nói nếu không khai báo kiểu thì có giá trị Empty là không chính xác!

Xin kiểm chứng cho Code này:

Mã:
Sub test()
    Dim sArr
    Dim Arr()
    MsgBox IsEmpty(sArr)
    MsgBox IsEmpty(Arr)
End Sub

Như vậy, biến sArr không khai báo, mặc nhiên nó là một biến Variant, thì nó mới chính là có giá trị Empty!
Thay vì test như vậy, ta test kiểu khác xem:
Mã:
Sub test()
    Dim sArr
    Dim Arr()
    MsgBox TypeName(sArr)
    MsgBox TypeName(Arr)
End Sub
Ẹc... Ẹc... --=0
-------------
Mà chắc sư phụ Mỹ đang nói đến các phần tử trong mảng chứ không phải nói nguyên 1 mảng?
 
Lần chỉnh sửa cuối:
Upvote 0
Mà sao Sư phụ có thể làm Sub đó phát sinh ra lỗi cũng đặc biệt thật luôn! Em thử hoài mà nó không ra lỗi!

Đoạn code đó để chụp hình giá trị Arr(2) = Empty thôi.
Nếu thêm câu lệnh này mới lỗi: Msgbox Arr() hoặc MsgBox Arr (tôi cũng ghi rất rõ mà? Kể cả câu tôi sai tôi cũng ghi rất rõ.)

Mà quý vị vui lòng trích dẫn ít thôi, trích cả đoạn code dài lê thê làm gì, đọc mệt quá. Với lại, nếu hỏi thêm thì viết bài mới, đừng edit bài cũ có khi tôi không đọc lại.
 
Upvote 0
Nhưng ít ra nó cũng rất đẹp và sáng code phải không Sư phụ!

Với bài này thì OK.
Tuy nhiên là một người mới tìm hiểu VBA nhưng mình thấy nên hạn chế tối đa sử dụng Goto vì những nhược điểm của nó như trong một bài viết của anh Siwtom
1. Goto "phá vỡ" trật tự, thứ tự của code, code trở nên rối rắm, khó hiểu, khó theo dõi, khó kiểm soát, khó tìm lỗi hơn.
2. Khi dùng goto thì cơ hội phạm lỗi sẽ lớn hơn. Điều này dễ hiểu vì code với goto rối rắm hơn nên ta dễ sơ ý, dễ không nhận thấy những vấn đề quan trọng.
3. Phân tích code có nhiều goto khó hơn nhiều do code rối rắm hơn.
Vì vậy đối với những người chập chững/ tìm hiểu căn bản VBA như mình xin vui lòng hướng dẫn và chia sẻ tỉ mỉ, rõ ràng hơn những kinh nghiệm đó. Tks
 
Lần chỉnh sửa cuối:
Upvote 0
Nhưng ít ra nó cũng rất đẹp và sáng code phải không Sư phụ!

Cái này không đúng nha!
Lập trình chuyên nghiệp tối kỵ Goto. Dù là người mới thì cũng nên hướng cho họ đi theo "hướng tốt"
Nghĩa cho rằng Goto là "sáng" và "đẹp" thì.. coi chừng... Ẹc... Ẹc...
 
Upvote 0
Thay vì test như vậy, ta test kiểu khác xem:
Ẹc... Ẹc... --=0

Việc test này nhằm kiểm tra giá trị của mảng có phải Empty hay không mà. Như tôi đã nói, mảng không có giá trị riêng, mà là tập hợp các giá trị của các phần tử.
Mảng không Empty vì nó chứa các phần tử, nhưng giá trị ban đầu của các phần tử sẽ là Empty.
 
Upvote 0
thông thường khi lập trình (tôi thường sử dụng C++) thì người ta thường dùng vòng lập để cho người ta nhập theo ý muốn của đề. rất ít khi thấy người ta dùng nhãn lắm
code của bạn tôi đã sửa lại theo những gì tôi hiểu và cũng đã test không thấy gì sai cả. nếu nhập không đúng abcd thì nó cũng thoát nếu người dùng bấm ok hay cancel lần 2.nói như bác siwtom. phải cho người ta cơ hội nhập lại chứ
tôi vẫn thích bạn dùng loop until
 
Lần chỉnh sửa cuối:
Upvote 0
thông thường khi lập trình (tôi thường sử dụng C++) thì người ta thường dùng vòng lập để cho người ta nhập theo ý muốn của đề. rất ít khi thấy người ta dùng nhãn lắm
Đúng là vậy, mỗi người làm theo quan điểm của mình, nếu không phát sinh lỗi và sáng code, dễ nhìn, dễ hiểu đặc biệt là phải hiệu quả, chính xác là OK. Tùy thói quen vậy!
 
Upvote 0
code của bạn tôi đã sửa lại theo những gì tôi hiểu và cũng đã test không thấy gì sai cả. nếu nhập không đúng abcd thì nó cũng thoát nếu người dùng bấm ok hay cancel lần 2.nói như bác siwtom. phải cho người ta cơ hội nhập lại chứ

Ý người ta nói hạn chế Goto thôi, đâu ai nói code sai
--------------------
Khẳng định: THÓI QUEN GOTO là thói quen XẤU. Không tin Nghĩa có thể search khắp google mà hỏi thử
--------------------
Một cái Goto chẳng nói gì. Thử tưởng tượng trong code có vài chục cái goto thì lúc test code chẳng biết đâu mà lần
Ngày trước khi mới tập tành, code tôi goto cả đống. Bây giờ rảnh rỗi mở ra phải sửa lại hết (vì nó khó theo dõi chứ chắng có "sáng" tí nào cả)
 
Lần chỉnh sửa cuối:
Upvote 0
...
Cho em hỏi luôn là Chr(10) & Chr(13) đều là ngắt xuống dòng, là một hay có gì khác nhau? Xin vui lòng giải thích

Nói trước. Ai không muốn nghe nói chuyện rườm rà thì bỏ qua, đừng đọc tiếp.

Bạn có thấy cái bàn máy đánh chữ thời thượng cổ qua chưa?

Trên bàn máy, có một cái trục cuốn giấy. Khi gõ một ký tự, trục này chạy về bên trái một nấc, lộ chỗ cho bạn gõ ký tự kế tiếp. Khi muốn sang dòng mới, hoặc khi chạy hết giới hạn về bên trái, tức là bạn đã gõ đủ một dòng, bạn phải kéo một cái cần cho trục này đi trở ngược về phía phải. Và đồng thời trục cuốn giấy lên một dòng. Kết quả là bạn sẽ tiếp tục gõ ở đầu dòng mới.

Cái trục này tên là "carriage". Động tác chạy về đầu dòng gọi là "carriage return", viết tắt là CR. Động tác cuốn lên dòng mới gọi là "line feed", viết tắt LF.

Trong quy ước ASCII, LF có giá trị 10 và CR giá trị 13.

Theo quy ước máy in thời thượng cổ, để sang một dòng mới cần phải có cả CR lẫn LF. Tuy nhiên phải tùy theo hệ thống mà người ta đặt CR trước hay LF trước.

Về sau này, các hệ điều hành mới có khi chỉ dùng một trong 2.

Vì vậy, các phần mềm mới có khuynh hướng dùng hằng số NewLine để tránh nói thẳng là CR hay LF. Các phần mềm xịn khi nạp qua các hệ điều hành khác nhau thì hệ thống tự đông thay đổi hằng số.

Tức là, theo nguyên tắc thì nên dùng NewLine hơn CR hay LF hay CRLF. Trên thực tế thì... tôi không rõ ông VBA theo tiêu chuẩn đến mức nào nên không dám nói thêm.

Thêm:

Khổng tử hỏi: Người trí là người thế nào, người nhân là người thế nào
Nhan hồi trả lời: Người trí là người biết mình, người nhân là người thương mình

Tánh tôi rất thương cái "tôi" của mình. Tôi lên diễn đàn mà không cho tôi nói về cái tôi của mình thì tôi cụt cả hứng. Mà đã cụt hứng thì tự giải vấn đề của mình còn bậy bạ nữa huống hồ gì của người khác.
 
Upvote 0
Nguyên văn bởi siwtom 1. Goto "phá vỡ" trật tự, thứ tự của code, code trở nên rối rắm, khó hiểu, khó theo dõi, khó kiểm soát, khó tìm lỗi hơn.
2. Khi dùng goto thì cơ hội phạm lỗi sẽ lớn hơn. Điều này dễ hiểu vì code với goto rối rắm hơn nên ta dễ sơ ý, dễ không nhận thấy những vấn đề quan trọng.
3. Phân tích code có nhiều goto khó hơn nhiều do code rối rắm hơn.



Vì vậy đối với những người chập chững/ tìm hiểu căn bản VBA như mình xin vui lòng hướng dẫn và chia sẻ tỉ mỉ, rõ ràng hơn những kinh nghiệm đó. Tks
Kinh nghiệm quý báu. Tks
 
Upvote 0
Ý người ta nói hạn chế Goto thôi, đâu ai nói code sai
--------------------

Khẳng định: THÓI QUEN GOTO là thói quen XẤU. Không tin Nghĩa có thể search khắp google mà hỏi thử
--------------------
Một cái Goto chẳng nói gì. Thử tưởng tượng trong code có vài chục cái goto thì lúc test code chẳng biết đâu mà lần
Ngày trước khi mới tập tành, code tôi goto cả đống. Bây giờ rảnh rỗi mở ra phải sửa lại hết (vì nó khó theo dõi chứ chắng có "sáng" tí nào cả)

Làm gì làm, nhưng "Pháp luật" không cấm là được! Nếu biết nó xấu thì "Anh Bill" đã không tạo ra nó rồi! Nhiều người không biết ứng dụng nên cứ bạ đâu goto bừa ra đấy, khi kiểm tra code thì rối ren, chồng chéo! Chẳng lẽ cái code có hơn chục dòng mà có Goto lại không kiểm soát được hay sao? Còn khi dùng 2 vòng lặp cùng lúc nếu muốn vòng lặp trong cho qua 1 vòng thì không thể không goto được!


Do
Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbYesNoCancel)
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" Or Kho = vbCanel)
Set Rng = Sheet4.Range("A3:A16")
If (Kho <> vbCanel) Then
......
end if

1) Trên nguyên tắc vbCancel là một Hằng số cho ra giá trị là 2, tuy nhiên bạn viết vbCanel (thiếu chữ c) nên nó là một biến, vì biến này không có khai báo nên nó là biến Variant (tức biến rỗng), tất nhiên với biến Kho dạng String thì khi Kho = "" cũng có nghĩa Kho = vbCanel . Vì thế bạn mới may mắn thoát được vòng lặp khi bạn bấm vào Cancel hay OK nhưng InputBox = "".

2) Cấu trúc của InputBox như thế này:

InputBox(prompt[, title] [, default] [, xpos] [, ypos] [, helpfile, context])

Vì vậy đặt hằng số vbYesNoCancel là hoàn toàn không hợp lý, và hằng số này nó có giá trị là 3.

Bạn phihndhsp chú ý cho điều này!
 
Lần chỉnh sửa cuối:
Upvote 0
Nhưng ít ra nó cũng rất đẹp và sáng code phải không Sư phụ!

Thứ nhất, Goto giới hạn dùng càng ít càng tốt.
Thứ hai, Goto trở về trước thì lại càng giới hạn dùng hơn.

Những cái này tuy không phải là chân lý nhưng hầu hết dân lập trình đều đồng ý. Không cần phải dẫn chứng.

Chính tôi cũng có code dùng Goto, nhưng tôi rất dè dặt khi dùng nó. 99% các trường hợp là để chạy về cuối hàm dọn dẹp rác rưởi trước khi thoát. Cos những trương hợp dùng exit sub/function không được vì code hiện khoá một số tài nguyên (*), cần phải giải toả trước khi thoát.

(*) ví dụ code bạn đặt Application trong tình trạng background update, trước khi thoát bạn phải đặt nó trở về trạng thái ban đầu.
 
Upvote 0
Làm gì làm, nhưng "Pháp luật" không cấm là được! Nếu biết nó xấu thì "Anh Bill" đã không tạo ra nó rồi! Nhiều người không biết ứng dụng nên cứ bạ đâu goto bừa ra đấy, khi kiểm tra code thì rối ren, chồng chéo! Chẳng lẽ cái code có hơn chục dòng mà có Goto lại không kiểm soát được hay sao? Còn khi dùng 2 vòng lặp cùng lúc nếu muốn vòng lặp trong cho qua 1 vòng thì không thể không goto được!

Vấn đề ở đây là gì?
Vì topic này mở ra để cho NGƯỜI MỚI học hỏi nên ta phải hướng cho họ hướng đi đúng
Để giải quyết bài toán này có hàng đống cách và goto cũng chẳng ai nói là sai hay dở hơn vòng lập... Chỉ là ta phải nên cảnh báo để cho người mới đừng quá lạm dụng
Nghĩa sành quá nên tự kiểm soát code được, người mới họ có làm được không?
Nên nhớ: kiểm tra code theo kiểu tuần tự từ trên xuống vẫn dễ dàng hơn rất nhiều so với "nhảy cóc" lung tung khắp nơi
Nếu biết nó xấu thì "Anh Bill" đã không tạo ra nó rồi
Tôi có nói GOTO của anh Bill là xấu đâu nhỉ? Tôi nói rõ thế này mà:
Khẳng định: THÓI QUEN GOTO là thói quen XẤU.

--------------------
Tôi nhắc lại: Nếu code Goto nằm ở 1 topic khác tôi sẽ không bàn đến, đừng để các thành viên mới tin tưởng để rồi tạo thành 1 thói quen xấu sau này
 
Upvote 0
2) Cấu trúc của InputBox như thế này:

InputBox(prompt[, title] [, default] [, xpos] [, ypos] [, helpfile, context])

Vì vậy đặt hằng số vbYesNoCancel là hoàn toàn không hợp lý, và hằng số này nó có giá trị là 3.

Bạn phihndhsp chú ý cho điều này!

Cả Nghĩa và Phi và cả mọi người chú ý cho điều này (sic, ẹc ẹc):

Trong cú pháp của InputBox không có chỗ cho tham số Buttons, do đó dù gán vbOKOnly, vbYesNo, vbYesNoCancel gì gì đi nữa, cũng là 2 nút OK Cancel.

Thứ 2, vbYesNoCancel không phải hằng số và cũng không có giá trị là 3.

Mấy đoạn code trong topic này, vài người khinh thường code quá căn bản nên cứ đọc suông rồi phán chứ không test.

Như tôi viết bài này, InputBox tôi học, sử dụng nhão nhừ, nhưng trước khi viết cũng phải viết 1 câu lệnh để test trước khi phán!
Rồi lại viết 1 câu MsgBox để xem trong cú pháp MsgBox, cái vbQuaiQuy là tham số gì, biết nó là tham số buttons để viết vào câu. Thế mà cũng có khi sai ấy chứ.
 
Lần chỉnh sửa cuối:
Upvote 0
Thứ nhất, Goto giới hạn dùng càng ít càng tốt.
Thứ hai, Goto trở về trước thì lại càng giới hạn dùng hơn.

Những cái này tuy không phải là chân lý nhưng hầu hết dân lập trình đều đồng ý. Không cần phải dẫn chứng.

Chính tôi cũng có code dùng Goto, nhưng tôi rất dè dặt khi dùng nó. 99% các trường hợp là để chạy về cuối hàm dọn dẹp rác rưởi trước khi thoát. Cos những trương hợp dùng exit sub/function không được vì code hiện khoá một số tài nguyên (*), cần phải giải toả trước khi thoát.

(*) ví dụ code bạn đặt Application trong tình trạng background update, trước khi thoát bạn phải đặt nó trở về trạng thái ban đầu.


Tôi không bảo thủ, nhưng tôi không ngán phải dùng GOTO và không có cảm giác sợ dùng GOTO như nhiều người nghĩ!

Lý do:

1) Khi có một nhãn (Label) thì cấu trúc của nó là một tên nhãn và dấu 2 chấm (:). Dù cho bạn có viết nó ở đâu đi chăng nữa nó cũng tự động chạy về sát rìa trái của cửa sổ soạn thảo, chính vì điều này bạn dễ dàng nhận biết code chắc chắn sẽ có GOTO.

2) Kiểm tra GOTO nằm tại đâu, đơn giản thôi, đã có Ctrl+H. Cứ Find Next là chúng "hiện nguyên hình" không sót một tên!

3) Nguyên tắc của tôi chỉ GOTO cho 1 đến 2 trường hợp và thường viết code chừng 20 dòng, nếu code nhiều hơn, tôi thường ngắt code ra thành nhiều thủ tục nhỏ, cho nên dù có GOTO nên cũng rất dễ phát hiện.

Cho đến nay tôi vẫn làm như thế và rất hài lòng là mình dám nghĩ dám làm, không theo tâm lý đám đông là như vậy.
 
Lần chỉnh sửa cuối:
Upvote 0
Thứ 2, vbYesNoCancel không phải hằng số và cũng không có giá trị là 3.

Ẹc ... ẹc ....

Em đố Sự phụ, cấu trúc này đặt tại đâu sẽ cho ra kết quả?

?vbYesNoCancel

Đồng thời, kính tặng Sư phụ bảng này luôn!
 

File đính kèm

  • KinhTang.jpg
    KinhTang.jpg
    177.1 KB · Đọc: 61
Lần chỉnh sửa cuối:
Upvote 0
Dùng goto đã khó hiểu, dùng goto để thay vòng lặp lại càng khó hiểu hơn.

Khi đọc code, thấy từ khoá DO, người ta tự động biết có một hiện tương lặp lại. Khi thấy một cái label, người ta chưng hửng: cái label này mình đã đọc qua cái Goto của nó chưa? code chỗ nào dẫn về đây? nó dùng để nhảy code hay dùng để lộn lại vòng lặp - tức là Goto tời hay lui?

Trên tình trạng lý tưởng, đường ràng ràng trước mặt, ai muốn đi bên trái bên phải gì cũng là quyền của họ. Trên thực tế, nếu đám đông đi bên phải mà mình đi bên trái là đi ngược bên. Hậu quả là cứ va mãi vào những người chiều kia.
 
Upvote 0
Ẹc ... ẹc ....
Em đố Sự phụ, cấu trúc này đặt tại đâu sẽ cho ra kết quả?
?vbYesNoCancel

Thứ nhất, khi diễn đạt trong câu văn viết về cú pháp, nó là tham số của inputBox chứ không phải hằng số.

Thứ hai, cái cửa sổ gõ dấu hỏi là chỗ nào thì tôi đã viết ra cách đây 5 năm trong topic Căn bản về For next.

Thứ ba, trong bài Nghĩa viết lời nhắc cho phihn, chắc chắn rằng ý Nghĩa muốn nói về "nếu giá trị trả về của InputBox là vbCancel" và có giá trị bao nhiêu, chứ không phải chủ ý nói đến vbYesNoCancel có giá trị bao nhiêu. vbYesNoCancel có giá trị bao nhiêu thì có ý nghĩa gì khi nói đến code của Phihn?
 
Lần chỉnh sửa cuối:
Upvote 0
Dùng goto đã khó hiểu, dùng goto để thay vòng lặp lại càng khó hiểu hơn.

Khi đọc code, thấy từ khoá DO, người ta tự động biết có một hiện tương lặp lại. Khi thấy một cái label, người ta chưng hửng: cái label này mình đã đọc qua cái Goto của nó chưa? code chỗ nào dẫn về đây? nó dùng để nhảy code hay dùng để lộn lại vòng lặp - tức là Goto tời hay lui?

Trên tình trạng lý tưởng, đường ràng ràng trước mặt, ai muốn đi bên trái bên phải gì cũng là quyền của họ. Trên thực tế, nếu đám đông đi bên phải mà mình đi bên trái là đi ngược bên. Hậu quả là cứ va mãi vào những người chiều kia.
NÓ ĐÁNG SỢ VẬY SAO? KAKAKAKAKA

Giống như Thầy ndu96081631 đang tìm cách diệt virus, tôi có tâm lý sợ bởi tôi không có cách trị, không biết nguyên tắc hoạt động của nó, không biết nó trú ẩn tại đâu nên tôi rất sợ, không dám nhìn tới nó, và tâm lý đám đông chắc cũng như tôi thôi, nhưng với Thầy thì đang tỏ ra rất khoái trá nếu ai đó "gửi tặng" Thầy một con virus!

Vậy Thầy có gì sai sao? Đi ngược với đám đông sao?

Quan trọng là ta phải biết cách để khống chế nó, hiểu được nguyên tắc hoạt động của nó! Ông bà ta có câu "Chớ thấy sóng cả mà ngã tay chèo" là như vậy!
 
Upvote 0
Xem ra, đối với HT Nghĩa bây giờ, thì đi ngủ là thượng sách. Giờ này càng viết, hắn càng không đọc kỹ. Và hắn lại edit bài để củng cố cái "xì trum" của hắn, dù chưa đọc bài trả lời của mình.
 
Upvote 0
Thứ nhất, khi diễn đạt trong câu văn viết về cú pháp, nó là tham số của inputBox chứ không phải hằng số.

Thứ hai, cái cửa sổ gõ dấu hỏi là chỗ nào thì tôi đã viết ra cách đây 5 năm trong topic Căn bản về For next.

Thứ ba, trong bài Nghĩa viết lời nhắc cho phihn, chắc chắn rằng ý Nghĩa muốn nói về "nếu giá trị trả về của InputBox là vbCancel" và có giá trị bao nhiêu, chứ không phải chủ ý nói đến vbYesNoCancel có giá trị bao nhiêu. vbYesNoCancel có giá trị bao nhiêu thì có ý nghĩa gì khi nói đến code của Phihn?
Hôm nay Sư phụ làm sao thế? Em đã nhắc tại bài #154 rồi nhưng vẫn không hiểu, đợi phải đưa cấu trúc lên!

Cho dù hằng số có nằm tại đâu vẫn là hằng số mà thôi vì hằng số không thay đổi giá trị như biến, điều này là tối thiểu. Cũng may khi bạn đó gán hằng số vbYesNoCancel lại nằm trong đối số [xpos] của InputBox nên mới không xảy ra lỗi đấy!

InputBox(prompt[, title] [, default] [, xpos] [, ypos] [, helpfile, context])
 

File đính kèm

  • KhongHopLy.jpg
    KhongHopLy.jpg
    156.7 KB · Đọc: 78
Lần chỉnh sửa cuối:
Upvote 0
Bạn hiền tuyệt lắm! Rất hợp với ý mình! Không cần dùng vòng lặp Do ... Loop để bẫy lỗi là như thế! Great!

Bạn thay Do ... Loop bằng cách dùng Goto và bạn mừng? Bản chất thì cũng là một loạt dòng code được thực hiện lặp đi lặp lại nhưng dùng Goto? Và bạn cho đó là code tốt hơn?

Thôi thì tùy mỗi người.

À mà bạn bỏ từ "bẫy lỗi" đi nhé. Code để phục phụ các chọn lựa khác nhau của người dùng đâu có dính dáng gì tới "bẫy lỗi"? Lỗi là những trường hợp vd. như: chia cho 0, mở tập tin không tồn tại, dùng đối tượng chưa được khởi tạo v...v Và để phục vụ những tình huống lỗi như thế thay vì nhìn thông báo hoặc "giết" trong Task Manager thì người ta viết code để xử lý. Đó mới là "bẫy lỗi". Chứ chuyện tôi chọn "a" hay "b" hay Cancel đâu có là lỗi gì?
 
Upvote 0
Hôm nay Sư phụ làm sao thế? Em đã nhắc tại bài #154 rồi nhưng vẫn không hiểu, đợi phải đưa cấu trúc lên!
Cho dù hằng số có nằm tại đâu vẫn là hằng số mà thôi vì hằng số không thay đổi giá trị như biến, điều này là tối thiểu.

InputBox(prompt[, title] [, default] [, xpos] [, ypos] [, helpfile, context])

Nghĩa phải đọc lại từng câu tôi viết, câu nào tôi cũng viết đủ ý nghĩa, kể cả câu sai mà tôi đã edit mờ đi và ghi chú sai. Một câu đơn giản là MsgBox Arr() bị lỗi mà Nghĩa cũng đi test sai code để nói là không lỗi.

Nhắc lại và giải nghĩa lại 1 câu liên quan:

Khi phát biểu 1 câu quan trọng để cho người mới học biết về cú pháp, phải dùng từ chính xác. InputBox() là 1 hàm, thì bên trong dấu ngoặc là các tham số (hoặc đối số) của hàm. Giá trị biến hoặc giá trị hằng cũng là tham số.

Khi nào nói đến việc viết tắt như End(3), Borders(7), InputBox (,,,3), .... thì mới nói đến giá trị hằng của tham số.

Nói đến vbOKQuaiQuy thì phải nói đến việc nó không phải là tham số của InputBox, chứ không phải nói giá trị của nó bằng bao nhiêu.


Đây là topic giới thiệu VBA căn bản cho người mới học, nên viết mọi thứ phải cân nhắc. Nếu sai phải nhận sai và ghi chú sai để người đọc biết và tránh. Cái mà ndu, siwtom, Vetmini, ... đều công nhận là không nên giới thiệu thì đừng cố bảo vệ quan điểm cá nhân trong topic này.

Còn việc gõ ?abc ở đâu hoặc tìm kiếm giá trị hằng ở bảng nào, Nghĩa có thể giới thiệu cho người mới, chứ đừng tặng tôi, vì tôi biết từ 5 năm trước đây rồi. Thí dụ tôi có thể hỏi Nghĩa có biết sử dụng object Browser hay không, chứ không phải Nghĩa hỏi ngược lại.
 
Upvote 0
Ẹc ... ẹc ....
Em đố Sự phụ, cấu trúc này đặt tại đâu sẽ cho ra kết quả?
?vbYesNoCancel
Đồng thời, kính tặng Sư phụ bảng này luôn!
Theo em hiểu: Anh Nghĩa đang nói đến đối số của MsgBox ở bảng này:
7.jpg
Nhưng anh Nghĩa chưa up luôn bảng Giá trị trả về của MsgBox:
8.jpg
Còn các tranh luận thì nói về InputBox.
Hình như không có giá trị trả về của InputBox khi nhấn các nút OK, Cancel.
Vấn đề GoTo mong là anh Nghĩa đừng bàn sâu vì đã mất 4, 5 trang rồi, bọn em khó nắm được cái cơ bản.
Sau mỗi bài tập, nhờ các Thầy chỉ cho chỗ thừa thiếu trong code, giải thuật, ... (đối với VBA cơ bản).
 
Upvote 0
Nói về Goto 1 chút:

Đúng là Goto ta nên hạn chế dùng, nhất là goto lên phía trên (dòng lệnh đã thực hiện)

Ai lại lấy Goto thay cho Do . . Loop Hay For . . . Next bao giờ!

Quả thật đây là lần đầu mình gặp & trong trường hợp là người hướng dẫn xài VBA kia đấy.

Riêng mình, xài Goto cũng có nhưng thường Goto xuống nhãn có bên dưới dòng lệnh Exit Sub

Và tôi nói thêm rằng, viết cho người khác xài tôi không bao giờ dùng Goto, thà rằng không viết còn hơn.

Với tôi Goto chỉ để dành xài riêng mà thôi.

Thân ái & cảm ơn các bạn đã chiếu cố đến bài.
 
Upvote 0
Theo em hiểu: Anh Nghĩa đang nói đến đối số của MsgBox ở bảng này:

Nhưng anh Nghĩa chưa up luôn bảng Giá trị trả về của MsgBox:

Còn các tranh luận thì nói về InputBox.
Hình như không có giá trị trả về của InputBox khi nhấn các nút OK, Cancel.
Vấn đề GoTo mong là anh Nghĩa đừng bàn sâu vì đã mất 4, 5 trang rồi, bọn em khó nắm được cái cơ bản.
Sau mỗi bài tập, nhờ các Thầy chỉ cho chỗ thừa thiếu trong code, giải thuật, ... (đối với VBA cơ bản).

Khi mới bắt đầu học lập trình, thì các cuốn sách luôn luôn dành các mục đầu tiên để nói về BIẾN & HẰNG SỐ. Đó là điều rất căn bản nên người mới lập trình phải tìm hiểu về nó, nên nó có được thảo luận tại đây cũng rất chính đáng.

Phải thống nhất với nhau rằng: Với những Hằng số có sẳn trong VBA thì dù nó là hằng số để dùng cho trường hợp nào đi chăng nữa ta vẫn có thể dùng nó được để thay thế giá trị (vấn đề là ta có muốn dùng hay không).

Hằng số là một giá trị không thay đổi, song với Biến thì có thể thay đổi được giá trị:

Mã:
Option Explicit
Private Const HangSo As Byte = 1
Private BienSo As Long


Sub Test3()
    HangSo = 3
    MsgBox HangSo
End Sub


Sub Test4()
    BienSo = 3
    MsgBox BienSo
    BienSo = 5
    MsgBox BienSo
End Sub

Với Test3 sẽ bị lỗi vì Hằng số không thể thay đổi giá trị được!

Khi một Biến, nếu không khai báo rõ ràng trong thủ tục và nếu biến đó lại trùng với tên của Hằng số có sẳn của VBA thì nó chính là Hằng số chứ không thể gọi là biến.

Chứng minh:

Mã:
[SIZE=4][COLOR=#ff0000][B]Option Explicit[/B][/COLOR][/SIZE]


Sub Test()
    Dim vbYesNoCancel As Long
    vbYesNoCancel = 10
    MsgBox vbYesNoCancel
End Sub


Sub Test2()
    MsgBox vbYesNoCancel
End Sub

Với Test1 khi chạy code, nó sẽ cho ra giá trị là 10 >>> Nó là một BIẾN

Với Test2 khi chạy code, nó sẽ cho ra giá trị là 3 >>> Nó là một HẰNG.

----------------------------------------------------------------------

Tôi đã góp ý với phihndhsp như thế này:

Do
Kho = InputBox("Ban chon loai san pham nao?" & Chr(13) & "(a,b,c,d)", "Chon loai", , vbYesNoCancel)
Loop Until (Kho = "a" Or Kho = "b" Or Kho = "c" Or Kho = "d" Or Kho = vbCanel)
Set Rng = Sheet4.Range("A3:A16")
If (Kho <> vbCanel) Then
......
end if

1) Trên nguyên tắc vbCancel là một Hằng số cho ra giá trị là 2, tuy nhiên bạn viết vbCanel (thiếu chữ c) nên nó là một biến, vì biến này không có khai báo nên nó là biến Variant (tức biến rỗng), tất nhiên với biến Kho dạng String thì khi Kho = "" cũng có nghĩa Kho = vbCanel . Vì thế bạn mới may mắn thoát được vòng lặp khi bạn bấm vào Cancel hay OK nhưng InputBox = "".

2) Cấu trúc của InputBox như thế này:

InputBox(prompt[, title] [, default] [, xpos] [, ypos] [, helpfile, context])

Vì vậy đặt hằng số vbYesNoCancel là hoàn toàn không hợp lý, và hằng số này nó có giá trị là 3.

Bạn phihndhsp chú ý cho điều này!

Và được Sư phụ comment như thế này:

Cả Nghĩa và Phi và cả mọi người chú ý cho điều này (sic, ẹc ẹc):

Trong cú pháp của InputBox không có chỗ cho tham số Buttons, do đó dù gán vbOKOnly, vbYesNo, vbYesNoCancel gì gì đi nữa, cũng là 2 nút OK Cancel.

Thứ 2, vbYesNoCancel không phải hằng số và cũng không có giá trị là 3.

Mấy đoạn code trong topic này, vài người khinh thường code quá căn bản nên cứ đọc suông rồi phán chứ không test.

Như tôi viết bài này, InputBox tôi học, sử dụng nhão nhừ, nhưng trước khi viết cũng phải viết 1 câu lệnh để test trước khi phán!
Rồi lại viết 1 câu MsgBox để xem trong cú pháp MsgBox, cái vbQuaiQuy là tham số gì, biết nó là tham số buttons để viết vào câu. Thế mà cũng có khi sai ấy chứ.

----------------------------------------------------------------------

Với phát biểu như dưới đây, tôi nghĩ là hoàn toàn không chính xác và nếu ai nói đó là chính xác thì người đó chưa hiểu gì về Hằng số!

Thứ 2, vbYesNoCancel không phải hằng số và cũng không có giá trị là 3.

Cho nên tôi mới phải tranh luận để người nào chưa nhận thức được điều này, nhất là những người mới học lập trình thấy rõ bản chất của Hằng và Biến.

Vì vậy, dù số đông có "ném đá" tôi vẫn tranh luận vì cái lẽ đúng.
 
Lần chỉnh sửa cuối:
Upvote 0
Khi mới bắt đầu học lập trình, thì các cuốn sách luôn luôn dành các mục đầu tiên để nói về BIẾN & HẰNG SỐ. Đó là điều rất căn bản nên người mới lập trình phải tìm hiểu về nó, nên nó có được thảo luận tại đây cũng rất chính đáng.

Phải thống nhất với nhau rằng: Với những Hằng số có sẳn trong VBA thì dù nó là hằng số để dùng cho trường hợp nào đi chăng nữa ta vẫn có thể dùng nó được để thay thế giá trị (vấn đề là ta có muốn dùng hay không).

Hằng số là một giá trị không thay đổi, song với Biến thì có thể thay đổi được giá trị:
.

Tôi thấy vầy:
Nghĩa đừng cố tranh luận nữa, tập trung vào chủ đề topic đi
Vì một sự thật hiển nhiên là: NGHĨA KHÔNG HIỂU NHỮNG GÌ MỌI NGƯỜI NÓI
(Ông nói Gà, bà nói Vịt)
Quay lại chủ đề chính, đừng để các thành viên mới sốt ruột
 
Upvote 0
Tôi thấy vầy:
Nghĩa đừng cố tranh luận nữa, tập trung vào chủ đề topic đi
Vì một sự thật hiển nhiên là: NGHĨA KHÔNG HIỂU NHỮNG GÌ MỌI NGƯỜI NÓI
(Ông nói Gà, bà nói Vịt)
Quay lại chủ đề chính, đừng để các thành viên mới sốt ruột
Một phát biểu chưa chính xác, nhất là phát ra từ "cây cao bóng cả" của diễn đàn thì nó nguy hiểm như thế nào đối với thành viên mới học hả Thầy? Em đang tranh luận nghiêm túc đó chứ? Mà thôi, em sẽ ngưng tranh luận tại đây, nếu thấy gì chưa đúng em cũng sẽ không tranh luận nữa vậy. Chấm dứt là chấm dứt, không dây dưa tí nào nữa!
 
Upvote 0
Với phát biểu như thế này tôi nghĩ là hoàn toàn không chính xác và nếu ai nói đó là chính xác thì người đó chưa hiểu gì về Hằng số!

Thứ 2, vbYesNoCancel không phải hằng số và cũng không có giá trị là 3.

Cho nên tôi mới phải tranh luận để người nào chưa nhận thức được điều này, nhất là những người mới học lập trình thấy rõ bản chất của Hằng và Biến.

Vì vậy, dù số đông có "ném đá" tôi vẫn tranh luận vì cái lẽ đúng.

Cái bản chất của hằng và biến, nếu Nghĩa muốn giới thiệu thì viết hẳn 1 bài để giới thiệu
Tương tự, Nghĩa viết hẳn 1 bài về giá trị ban đầu của biến có khai báo kiểu và không khai báo kiểu
Và, viết hẳn 1 bài về giá trị các hằng

Chứ không phải viết về 1 giá trị hằng nào đó trong cú pháp hàm và tham số.

Còn câu của tôi mà Nghĩa trích, tôi đã viết vắn tắt sau đó giải nghĩa lại, mà Nghĩa không đọc, hoặc đọc không hiểu, hoặc cố tình không nói đến.


Tóm lại thế này, Nghĩa mở 1 topic với một ý định tốt và đáng quý. Nếu trong tất cả các câu đố, bài giải, bài giới thiệu thêm, Nghĩa viết đúng chuẩn, không cố bảo vệ ý kiến của mình, biết tự nhận sai, hoặc nhận rằng không phù hợp trong topic này, ... thì tôi và cả ndu, anh siwtom, vetmini, ... cũng sẽ không tham gia (đừng nói ném đá, tôi không ném đá và chẳng ai ném đá ai hết)


Thí dụ bài nhận xét của Nghĩa về các bài trả lời tham gia câu đố Ending-Test, bài nhận xét đó đúng, thì có ai phản đối bài đó đâu?

Tôi và các anh ấy tham gia vì nhận thức vấn đề quan trọng của topic này, cái gì nên giới thiệu và cái gì không (Goto), nên dùng cái gì trong trường hợp nào (Do – Loop thay cho For 1 triệu lần), … nếu người mới học vào đây đọc và làm theo tất tần tật thì hậu quả thế nào? Trong khi đó Nghĩa là người tạo topic lại không nhận thức được như thế: Góp ý không nhận, sai không sửa, bài góp ý không đọc.

Vậy từ giờ, tôi không tham gia topic này nữa. Nhưng tôi vẫn sẽ theo dõi, và nếu nhận ra rằng bài hướng dẫn nào có thể làm cho người mới học đi sai đường, bài nào viết sai câu cú có thể gây hiểu sai, tôi xóa. Nếu không xóa thì người học thực hành sai, ảnh hưởng đến cả GPE, vì họ học cái sai từ GPE.
 
Upvote 0

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

Back
Top Bottom