Đố vui về VBA!

Liên hệ QC

anhtuan1066

Thành viên gạo cội
Tham gia
10/3/07
Bài viết
5,802
Được thích
6,905
Nhằm cũng cố kiến thức về VBA cho các bạn mới bắt đầu và cả những bạn đang ứng dụng mà chưa hiểu nhiều về nó, tôi mở topic này với mong mõi qua những câu hỏi vui, các bạn sẽ nhận định lại sự hiểu biết cũa mình... (Kễ cã chính tôi cũng đang tập tành nên có rất nhiều cái chưa biết)
Mong rằng topic sẽ mang đến cho các bạn những khám phá thú vị với những cái tưỡng chừng như đã biết
Mong nhận dc bài viết về câu đố cũa các cao thủ! Còn các bạn mới thì đừng ngại khi đưa ra ý kiến cũa mình.. Có sai có sữa sẽ hoàn thiện!
Tôi xin mỡ màn trước bằng 1 câu hỏi đơn giãn
ANH TUẤN

CÂU HỎI 1: Tại sao biến K ko hoạt động?
Tôi muốn khi nhấn vào 1 button thì cell A1 sẽ tăng lên 1 đơn vị... Tôi đã làm như sau:
-Tạo 1 Command Button (nút nhấn thuộc thanh Control Toolbox), click phải chuột lên nút nhấn, chọn View code, rồi gõ vào đoạn code sau:
PHP:
Private Sub CommandButton1_Click()
   K = K + 1
   Range("A1").Value = K
End Sub
Ban đầu K chưa có gì, xem như =0, nhấn nút lần thứ nhất thì K dc tăng thêm 1, vậy K hiện tại sẽ bằng 1, và gán K vào cell A1 thì đương nhiên A1 sẽ =1... Nhấn nút lần 2, K lại dc tăng thêm 1 nên hiện tại K sẽ =2 và cell A1 cũng sẽ =2... vân vân.. từ đó diễn tiến tiếp...
Hi.. hi.. Điều này nghe qua có vẽ rất hợp lý, ấy thế mà khi nhấn nút nó chỉ hoạt động dc duy nhất 1 lần (A1 = 1) rồi thôi ko nhút nhít nữa...
Các bạn có thể giãi thích tại sao lại như thế ko? Tại sao những lần nhấn nút sau đó K lại ko tăng thêm tí nào (vì thực tế A1 vẫn cứ = 1 hoài) ?
ANH TUẤN
 
Áp thuộc tính như thế nào nhỉ?
Anh cho cái dưới vào Workbook
Option Explicit
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Application.OnTime Now + TimeValue("00:00:10"), "Module1.OpenMe"
ThisWorkbook.Close True
End Sub

Cho cái dưới vào Module 1
Public Sub OpenMe()
MsgBox "I'm Back"
End Sub

Save lại dạng xlsm. Và bấm bấm chạy code hoặc dấu X
 
Upvote 0
Anh cho cái dưới vào Workbook
Option Explicit
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Application.OnTime Now + TimeValue("00:00:10"), "Module1.OpenMe"
ThisWorkbook.Close True
End Sub

Cho cái dưới vào Module 1
Public Sub OpenMe()
MsgBox "I'm Back"
End Sub

Save lại dạng xlsm. Và bấm bấm chạy code hoặc dấu X
Sau khi mở lại nó hiện cái bảng này. Tôi Disable là hết chuyện.

upload_2018-2-7_17-17-55.png
 
Upvote 0
Range("D3:E5") nằm ở chỗ nào?
------------------------------------
Đầu tiên các bạn chạy thử code này:
Mã:
Sub Test()
  Range("C4:M100").Range("B2:J10").Range("D3:E5").Select
End Sub
rồi xem kết quả
Ai đó nói cho mọi người biết tại sao lại như vậy
(câu đố này không hẳn là đố, chỉ vui vui thôi)
 
Upvote 0
Range("D3:E5") nằm ở chỗ nào?
------------------------------------
Đầu tiên các bạn chạy thử code này:
Mã:
Sub Test()
  Range("C4:M100").Range("B2:J10").Range("D3:E5").Select
End Sub
rồi xem kết quả
Ai đó nói cho mọi người biết tại sao lại như vậy
(câu đố này không hẳn là đố, chỉ vui vui thôi)
Tại sao nó lại ra [g7:h9] ................ khó hiểu :D
 
Upvote 0
PHP:
Sub GPETest()
  Range("C4:M100").Range("B2:J10").Range("D3:E5").Select
  MsgBox Range("C4:M100").Range("B2:J10").Address, , Selection.Address
End Sub
 
Upvote 0
Tại sao nó lại ra [g7:h9] ................ khó hiểu :D
Ai cũng có 1 kho kiến thức ngay bên cạnh mà không chịu sử dụng. Bó tay.

1. Range(*) là vùng được xác định trong hệ tọa độ của trang tính - hệ tọa độ có gốc nằm tại ô A1 của trang tính.
2. Range(*).Range(**) là vùng được xác định trong hệ tọa độ của vùng Range(*) - hệ tọa độ có gốc nằm tại ô đầu tiên (góc trái bên trên) của vùng Range(*). Tức khi xác định địa chỉ của Range(*).Range(**) thì coi ô đầu tiên (góc trái bên trên) của vùng Range(*) là A1 ***
-------------
1. Range("C4:M100") -> vùng có 97 dòng và 11 cột, bắt đầu từ ô ở dòng 4 cột 3 của trang tính - ô C4
2. Theo ***
Range("C4:M100").Range("B2:J10") -> vùng có 9 dòng và 9 cột, bắt đầu từ ô ở dòng 2 cột 2 (B2) trong hệ tọa độ của Range("C4:M100") - hệ tọa độ có gốc tại C4 của trang tính. Vùng này bắt đầu từ dòng 2 cột 2 (B2) trong hệ tọa độ trên, tức bắt đầu từ ô D5 trong hệ tọa độ trang tính. Tính từ D5 lấy 9 hàng 9 cột thì ta có D5:L13 là địa chỉ của vùng Range("C4:M100").Range("B2:J10") (trong hệ tọa độ của trang tính)
3. Theo ***
Range("C4:M100").Range("B2:J10").Range("D3:E5") -> vùng có 3 dòng và 2 cột, bắt đầu từ ô ở dòng 3 cột 4 (D3) trong hệ tọa độ của Range("C4:M100").Range("B2:J10") - hệ tọa độ có gốc tại D5 của trang tính. Vùng này bắt đầu từ dòng 3 cột 4 (D3) trong hệ tọa độ trên, tức bắt đầu từ ô G7 trong hệ tọa độ trang tính. Tính từ G7 lấy 3 dòng và 2 cột thì ta có G7:H9 là địa chỉ của vùng Range("C4:M100").Range("B2:J10").Range("D3:E5") (trong hệ tọa độ của trang tính)
 

File đính kèm

  • 54321.JPG
    54321.JPG
    152.1 KB · Đọc: 13
  • 12345.JPG
    12345.JPG
    116 KB · Đọc: 14
Upvote 1
Mã:
Sub Functions()
    Dim k As Range
    Set k = Range("a1")
    MsgBox k(1)(3)(5)(7)(9)....(101)().Address
End Sub

Thế đố mọi người đoạn code trên sẽ hiện ra cái gì, lưu ý .... có nghĩ là còn rất nhiều(11)(13)(15)
 
Upvote 0
Em đang suy nghĩ ứng dụng nó vô việc chi đây ? hay chỉ chơi vui vậy thôi
Nó có vô số ứng dụng nếu anh có hoa tay cùng trí tưởng tượng.

Ví dụ cho một biến Range xxx nào đó, yêu cầu truy xuất vào ô đầu tiên của xxx thì sẽ là xxx.Range("A1")
 
Upvote 0
Em đang suy nghĩ ứng dụng nó vô việc chi đây ? hay chỉ chơi vui vậy thôi
Có ứng dụng khi hiểu về nó: tương đối của tương đối của tương đối của ...tương ...
mãi thành của tương cà ...
Nói vui vậy, còn

Cái này rất hiểu ích khi chúng ta sử dụng biến range RG, thì khi xét bên trong nó ta chỉ cần RG(1,1),RG(1,2).....vv rối có thể trong RG lại có vùng rng nữa thì cũng tương tự
(ở đây sử dụng địa chỉ theo hàng, cột)
 
Upvote 0
Em đang suy nghĩ ứng dụng nó vô việc chi đây ? hay chỉ chơi vui vậy thôi
Có nhiều cách viết, tùy trường hợp mà chọn cách nào thuận tiện. Không có cách nào bắt buộc phải có - mỗi cách đều có thể thay bằng cách khác. Vd. Thay vì Range(*).Range(**).Range(***) có thể dùng offset hoặc/và Resize
Vd. thay cho

Range("C4:M100").Range("B2:J10").Range("D3:E5")

có thể dùng

Range("C4:M100").Offset(1, 1).Offset(2, 3).Resize(3, 2)

hoặc gộp 2 Offset làm một

Range("C4:M100").Offset(3, 4).Resize(3, 2)
 
Upvote 0
Có nhiều cách viết, tùy trường hợp mà chọn cách nào thuận tiện. Không có cách nào bắt buộc phải có - mỗi cách đều có thể thay bằng cách khác
Em thấy Offset với chỉ số âm thì không viết theo kiểu Range được.
Trong trường hợp vùng cần xác định có nhiều Areas thì dùng Range sẽ gọn hơn (nhưng thực tế cũng chưa thấy trường hợp nào)
PHP:
Selection.Range("B2,A3,C4,B6,D5,D9,C11,A9,B13").Select
 
Upvote 0
Em thấy Offset với chỉ số âm thì không viết theo kiểu Range được.
Thực ra tôi viết mà chưa nghĩ sâu. Xin rút lại :D
Range.Range đúng là thay được bằng Offset
Mã:
Sub hehe()
     MsgBox Selection.Range("B2,A3,C4,B6,D5,D9,C11,A9,B13").Address & vbCrLf & _
        Range("B2,A3,C4,B6,D5,D9,C11,A9,B13").Offset(Selection.Row - 1, Selection.Column - 1).Address
End Sub
Nhưng ngược lại thì với chỉ số âm thì không được.

Thôi thì: có nhiều trường hợp có thể dùng nhiều cách, và tùy trường hợp mà dùng cách thuận tiện.

Bản thân tôi rất hay dùng Offset + Resize - rất trực quan. rng.Offset thì tưởng tượng được rng được dịch chuyển thế nào. Rồi muốn nó có bao nhiêu dòng cột thì Resize. Bởi trong rất nhiều trường hợp, nhưng không phải tất cả, ta có sự dịch chuyển tương đối, vd.: "sau khi tìm được ô xyz (ô tên) rồi thì dịch sang phải 2 cột (đến cột số lượng) xuống dưới 1 dòng (số lượng đầu tiên dưới dòng tên 1 dòng) và đọc kết quả vùng có r dòng.
 
Lần chỉnh sửa cuối:
Upvote 0
Mỗi Rang lúc này là một Thuộc tính. Có khả năng thừa kế của object trước. Object mục tiêu là : D3:E5. Nó thừa kế column, row của ojbect trước. Vì excel xác định toạ độ theo góc trên, bên trái nên nó sẽ căn cứ C4, B2 để dịch chuyển.


RangeA.RangeB.RangC.Select
RangeA
Range(B2): (2,2) => Object Range(B2)

RangeA.RangeB
Range(B2).Range(C4)= (2+3-1, 2+4-1) =(4, 5) =>Object Range(D5)

RangeA.RangeB.RangC
Range(B2).Range(C4).Range(D3) = Range(D5).Range(D3) =(4+4-1, 5+3-1) = (7, 7) => Object Range(G7)

RangeA.RangeB.RangC.Select
Range(G7).Select
 

File đính kèm

  • runhcm.png
    runhcm.png
    126.2 KB · Đọc: 4
Lần chỉnh sửa cuối:
Upvote 0
Em có ngu ý chút về Range và Offset.

Offset(RowOffset, ColumnOffset) As Range
Offset là một Function truyền hai đối số (paramater) RowOffset, ColumnOffset trả về Object Range (As Range)

Rang(Cell1, Cell2) As Range
Range là một Object nhưng là Property (Thuộc tính) nếu được chỉ định Rõ bởi (WB, WS) nếu không chỉ định nó sẽ lấy ThisWookBook.ActiveSheet.Range

Resize(RowSize, ColumnSize) As Range
Resize là một Function truyền hai đối số (paramater) RowSize, ColumnSize trả về Object Range (As Range)

RangeA.RangeB.RangeC
Bản thân RangeC là một Object, nhưng
RangeC là Property của RangeB(Object) thừa kế các thuộc tính từ RangeB
RangeB là Property của RangeA(Object) thừa kế các thuộc tính từ RangeA
Nên lệnh: RangeC.Select thì trả về Object là RangeC
Lệnh RangeB.RangeC.Select thì trả về Trả về Object của RangeC có thừa kế thuộc tính từ RangeB, sau đó sẽ Select.
Như vậy, nếu trong bộ nhớ: Các quá trình định địa chỉ thì RangeA, RangeB, RangeC là Object được lưu trong HEAP, nhưng quá trình tính toàn chuyển đổi
RangeA.RangeB.RangeC lại thực hiện trong phần STACK(Nhớ tạm), Sau quá trình chuyển đổi lại lưu vào HEAP (Toàn chương trình)

Với Range.Offset thì Function Offset này sẽ được gọi ra trong phần STACK chứ nó không có lưu trong HEAP, sau khi kết thúc quá trình gọi sẽ mất nên cần Range chứa nó, và Range.Offset trả về sẽ được lưu vào HEAP. Tương tự cho Phần Resize. Nhưng,
Resize: Sẽ trả về kích cỡ cho Range chỉ định
Offset: Sẽ thay đổi Index của Range chỉ định.

Lệnh Range.Offset.Resize thì Sau khi Function Offset thực hiện sẽ trả về Range đó (có thay đổi theo Offset. Chuyển dịch) tiếp tục thực hiện Function Resize(Thay đổi lại kích cỡ).

Cho nên cách nào cũng được, nhưng cách tối ưu theo em nghĩ là Range.Offset.Resize vì nó sử dụng Function nên dễ hình dung và cũng giảm thiểu được bộ nhớ khi sử dụng, và chỉ định một địa chỉ Range gốc thôi.
 
Lần chỉnh sửa cuối:
Upvote 0
...
Như vậy, nếu trong bộ nhớ: Các quá trình định địa chỉ thì RangeA, RangeB, RangeC là Object được lưu trong HEAP, nhưng quá trình tính toàn chuyển đổi
RangeA.RangeB.RangeC lại thực hiện trong phần STACK(Nhớ tạm), Sau quá trình chuyển đổi lại lưu vào HEAP (Toàn chương trình)

Với Range.Offset thì Function Offset này sẽ được gọi ra trong phần STACK chứ nó không có lưu trong HEAP, sau khi kết thúc quá trình gọi sẽ mất nên cần Range chứa nó, và Range.Offset trả về sẽ được lưu vào HEAP. ...

Hiểu chết liền.
 
Upvote 0
Em có ngu ý chút về Range và Offset.

Offset(RowOffset, ColumnOffset) As Range
Offset là một Function truyền hai đối số (paramater) RowOffset, ColumnOffset trả về Object Range (As Range)

Rang(Cell1, Cell2) As Range
Range là một Object nhưng là Property (Thuộc tính) nếu được chỉ định Rõ bởi (WB, WS) nếu không chỉ định nó sẽ lấy ThisWookBook.ActiveSheet.Range

Resize(RowSize, ColumnSize) As Range
Resize là một Function truyền hai đối số (paramater) RowSize, ColumnSize trả về Object Range (As Range)

RangeA.RangeB.RangeC
Bản thân RangeC là một Object, nhưng
RangeC là Property của RangeB(Object) thừa kế các thuộc tính từ RangeB
RangeB là Property của RangeA(Object) thừa kế các thuộc tính từ RangeA
Nên lệnh: RangeC.Select thì trả về Object là RangeC
Lệnh RangeB.RangeC.Select thì trả về Trả về Object của RangeC có thừa kế thuộc tính từ RangeB, sau đó sẽ Select.
Như vậy, nếu trong bộ nhớ: Các quá trình định địa chỉ thì RangeA, RangeB, RangeC là Object được lưu trong HEAP, nhưng quá trình tính toàn chuyển đổi
RangeA.RangeB.RangeC lại thực hiện trong phần STACK(Nhớ tạm), Sau quá trình chuyển đổi lại lưu vào HEAP (Toàn chương trình)

Với Range.Offset thì Function Offset này sẽ được gọi ra trong phần STACK chứ nó không có lưu trong HEAP, sau khi kết thúc quá trình gọi sẽ mất nên cần Range chứa nó, và Range.Offset trả về sẽ được lưu vào HEAP. Tương tự cho Phần Resize. Nhưng,
Resize: Sẽ trả về kích cỡ cho Range chỉ định
Offset: Sẽ thay đổi Index của Range chỉ định.

Lệnh Range.Offset.Resize thì Sau khi Function Offset thực hiện sẽ trả về Range đó (có thay đổi theo Offset. Chuyển dịch) tiếp tục thực hiện Function Resize(Thay đổi lại kích cỡ).

Cho nên cách nào cũng được, nhưng cách tối ưu theo em nghĩ là Range.Offset.Resize vì nó sử dụng Function nên dễ hình dung và cũng giảm thiểu được bộ nhớ khi sử dụng, và chỉ định một địa chỉ Range gốc thôi.
Hiểu chết liên! Thật lòng bạn mong ước gì thì hãy nói ra, chứ cứ để mọi người thẹn thùng là không được.

Gợi ý là range offset resize đều là thuộc tính chứ không phải là hàm đâu nhé.
 
Upvote 0
Web KT
Back
Top Bottom