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
Thì cũng đúng! Vậy tùy trường hợp mà chọn lựa thôi
Chính vì sợ nó chạy liên tục nên tôi có đoạn If TypeName(cbox) = "Nothing"Then rồi đó. Bảo đảm chỉ chạy 1 lần
(không tin có thể dùng MsgBox đặt trong If để thử)
Vậy: Khi nào code gì đó bị lỗi thì cbox sẽ = Nothing và sự kiện MouseMove sẽ tự khởi tạo lại nó
------------------
Mà công nhận chú Nghĩa này tiếp cận vấn đề nhanh ghê! Gợi ý 1 chút đã làm trúng phốc!
Ẹc... Ẹc...
Em nhìn thấy code Thầy chứ! Tuy nhiên Thầy đặt con trỏ vào frame rồi để y đó, không cần rê đi đâu hết, Thầy sẽ thấy màn hình nó chớp chớp nhanh liên tục và dung lượng máy tăng lên thấy rõ rệt (theo máy em); Thầy thử test xem sao!
Em có cảm giác thế này, mà hình như là đúng: Việc ta vẽ lên sheet 1 cái Frame rồi đặt Combobox lên đó thì ComboBox hiển thị được tiếng Việt Unicode, cũng giống như đặt ComboBox này lên trên Form vậy. Như vậy cái Frame này đóng vai trò như một "điểm tựa" cho anh ComboBox đặt chân. Từ đó có thể suy ra rằng ta có thể thay Frame bởi MultiPage cũng được.
Em nhìn thấy code Thầy chứ! Tuy nhiên Thầy đặt con trỏ vào frame rồi để y đó, không cần rê đi đâu hết, Thầy sẽ thấy màn hình nó chớp chớp nhanh liên tục và dung lượng máy tăng lên thấy rõ rệt (theo máy em); Thầy thử test xem sao!
Thì cũng giống như khi ta dùng sự kiện Change hoặc SelectionChange thôi. Dù ta đã "rào" bằng dòng lệnh If Target.Address = ... hoặc If Not Intersect(....).... gì gì đó thì code sự kiện nó vẫn có công đoạn kiểm tra vậy? Kiểm tra đúng điều kiện thì nó làm, không thì... nghỉ
Vậy chẳng lẽ vì sợ cái vụ kiểm tra ấy mà ta nghỉ xài Change và SelectionChange?
---------------------------
Còn cái vụ chớp chớp giật giật gì đó thì máy tôi không bị, CPU trong Task Manager cũng không tăng (cần quay phim màn hình để chứng minh không?)
---> Suy ra: Máy Nghĩa cùi bắp? Ẹc... Ẹc...
Còn cái vụ chớp chớp giật giật gì đó thì máy tôi không bị, CPU trong Task Manager cũng không tăng (cần quay phim màn hình để chứng minh không?)
---> Suy ra: Máy Nghĩa cùi bắp? Ẹc... Ẹc...
Tải file về xem cả buổi mà chẳng hiểu cách làm thế nào để ra được như thế. Hic, tạm thời lưu file khi nào cần thì cứ đem ra xào nấu lại. Em nghĩ chắc cũng có người giống em thôi. Nếu không phải thì chắc là mình thuộc dạng .... nhất.
Cái vụ chớp màn hình gì đó thì máy tính mình cũng không bị. Anh Nghĩa thay máy tính đi nha
Nó chớp rất nhẹ chứ không phải giật giật, ở trên Window Caption đấy, không chớp trong sheet đâu! Window7 cái caption nó gần như trong suốt nên dễ nhận ra điều này.
Còn đây là "hàng" của mình đây! Làm theo hướng MouseMove (canh rất chuẩn mới không thấy "em" Frame)! Xài bằng RowSource nên không Add List!
[GPECODE=vb]
Option Explicit
Private WithEvents Fra1Cbx1 As MSForms.ComboBox
Private IsCheck As Boolean
''---------------------------------------------------
'' New version!
''---------------------------------------------------
Private Sub Frame1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If IsCheck Then Exit Sub
Set Fra1Cbx1 = Sheet1.Frame1.Controls("ComboBox1")
IsCheck = True
End Sub
Private Sub Fra1Cbx1_Change()
MsgBox Fra1Cbx1
End Sub[/GPECODE]
Đối tượng Combobox của bạn trong video này là đối tượng ở trên Userform, nhưng nó là một ActiveX Control, đặt trên Form thì không vấn đề gì rồi, nhưng bạn thử chèn nó lên Sheet xem nào. Lưu ý khi chèn trên Sheet có 2 loại đối tượng ComboBox đấy nhé, 1 cái thuộc Form Controls, 1 cái thuộc ActiveX Controls. Trong video trước của bạn tại bài #897 là ComboBox của Form Controls, mà với cái này thì cứ để thiết lập mặc định của Windows cũng hiển thị được tiếng Việt Unicode.
cái Anh hỏi là cái này chắc ko sai chứ ???? đưa file lên Youtube nó cứ đòi đợi xử lý lâu quá
[video=youtube;M7wvk_utY9g]http://www.youtube.com/watch?v=M7wvk_utY9g[/video]
Mặc định là trong hộp ActiveX Controls là không có anh này rồi đó!
Vẽ tại đâu?
Bấm vào nút More Controls (cái biểu tượng cái búa và cái khóa chéo nhau).
Sau khi hộp More Controls hiện ra, chọn vào mục Microsoft Form 2.0 Frame, rồi OK, sau đó ta vẽ trên sheet cái Frame này.
2) Add Controls lên Frame:
Trong chế độ Design, Click phải chuột vào Frame, chọn Frame Objects > Edit
Lúc này ta sẽ có được một ToolBox để add vào Frame.
3) Tạo thuộc tính cho Controls:
Click phải vào control, lúc này có một menu hoàn toàn xa lạ, kệ nó, ta chọn vào mục Properties của nó.
Lưu ý, cái Properties này nó cũng khác với các Properties ta vẫn thường thấy, đó là ta chọn một thuộc tính bất kỳ trong đó, thì nó sẽ hiện giá trị lên trên một Combobox ở trên cùng, muốn thay đổi gì thì thay đổi tại ComboBox đó rồi bấm nút Apply.
4) Bước này là tạo sự kiện cho nó, tạo như thế nào thì đã có các bài nói về chúng rồi, mình không nói lại nữa.
Private Sub Frame1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If TypeName(cbox) = "Nothing" Then
Set cbox = Sheet1.Frame1.Controls("ComboBox1")
cbox.List() = Sheet1.Range("A1:A10").Value
End If
End Sub
Với code trên, theo em nghĩ nó vẫn chưa tối ưu. Khi một code bị lỗi, ngay lập tức TẤT CẢ CÁC BIẾN sẽ giải phóng bộ nhớ hoàn toàn và trả về dạng mặc định của chúng (kể cả biến boolean cũng sẽ trả về dạng False). Vì thế việc Set lại cbox là hoàn toàn hợp lý. Nhưng việc nạp lại List cho cbox thì thừa là vì dù cho có lỗi cách nào đi chăng nữa thì List của nó không thay đổi, có nghĩa là list của nó không tự clear, cho nên ta mỗi lần Set là mỗi lần nạp list sẽ không hay. Nếu dữ liệu ngần ấy thì chả nói gì, còn như dữ liệu vài chục ngàn dòng, sau đó Filter, tính toán, Unique v.v... rồi mới tạo ra một cái Array để gán vào thì hơi ẹc ẹc ...
Theo em thì nên làm như vầy:
Mã:
Private Sub Frame1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If TypeName(cbox) = "Nothing" Then
With Frame1.Controls("ComboBox1")
[B][COLOR=#0000ff] If .ListCount = 0 Then .List = Range("A1:A10").Value[/COLOR][/B]
End With
Set cbox= Frame1.Controls("ComboBox1")
End If
End Sub
Có như vậy thì ta chỉ việc Set cho cbox thôi mà không cần phải nạp lại list một lần nào nữa!
Returns a string created by joining a number of substrings contained in an array.
Syntax:
Join(sourcearray[, delimiter])
sourcearray: Required.One-dimensional array containing substrings to be joined.
delimiter: Optional. String character used to separate the substrings in the returned string. If omitted, the space character (" ") is used. If delimiter is a zero-length string (""), all items in the list are concatenated with no delimiters.
Chắc có lẽ không ai thèm trả lời câu hỏi này, vậy mình "tự sướng" luôn! (tự sướng nên chả thú vị gì cả).
Các kiểu khai báo mảng thuộc kiểu String, Variant, hoặc không khai báo loại nào đều không phát sinh lỗi:
Mã:
Sub Test1()
Dim i As Byte
Dim Arr(0 To 9)
Dim sArr(0 To 9) As String
Dim vArray(0 To 9) As Variant
For i = 0 To 9
Arr(i) = i
sArr(i) = i
vArray(i) = i
Next
MsgBox Join(Arr, ",")
MsgBox Join(sArr, ",")
MsgBox Join(vArray, ",")
End Sub
Nhưng những dạng khai báo là kiểu Numeric (Byte, Long, Double, Date, v.v...) thì sẽ bị "dính chưởng"!
Mã:
Sub Test2()
Dim i As Byte
[COLOR=#008000]'Tat ca bien kieu Numeric deu bi loi:[/COLOR]
[B][COLOR=#0000cd]Dim Arr(0 To 9) As Long [/COLOR][COLOR=#008000]' Integer, Byte, Double, Currency, Date v.v...[/COLOR][/B][COLOR=#0000cd][/COLOR]
For i = 0 To 9
Arr(i) = i
Next
MsgBox Join(Arr, ",")
End Sub
Vì thế, khi thực hiện hàm Join thì người lập trình phải luôn luôn nằm lòng vấn đề này để mà tránh vì đôi khi mình đã làm đúng với cấu trúc rồi, nhưng tại sao lại phát sinh lỗi thì là do "kiểu nó như thế".
Nhưng những dạng khai báo là kiểu Numeric (Byte, Long, Double, Date, v.v...) thì sẽ bị "dính chưởng"!
Mã:
Sub Test2()
Dim i As Byte
[COLOR=#008000]'Tat ca bien kieu Numeric deu bi loi:[/COLOR]
[B][COLOR=#0000cd]Dim Arr(0 To 9) As Long [/COLOR][COLOR=#008000]' Integer, Byte, Double, Currency, Date v.v...[/COLOR][/B]
For i = 0 To 9
Arr(i) = i
Next
MsgBox Join(Arr, ",")
End Sub
Vì thế, khi thực hiện hàm Join thì người lập trình phải luôn luôn nằm lòng vấn đề này để mà tránh vì đôi khi mình đã làm đúng với cấu trúc rồi, nhưng tại sao lại phát sinh lỗi thì là do "kiểu nó như thế".
Bạn căn cứ vào đâu để nghĩ rằng: "vì đôi khi mình đã làm đúng với cấu trúc rồi"?. Bạn lấy cái gì ra làm chuẩn, tiêu chí nào để nghĩ là mình làm đúng?.
Người lập trình nào cũng phải đọc help. Dù là ngôn ngữ nào. Không chịu đọc help thì quên luôn chuyện lập trình đi.
Mà đọc help thì phải tìm mọi cách để hiểu. Có người cũng đọc help nhưng cóc hiểu hoặc không hiểu chính xác, không hiểu hết.
Bạn đã đọc về hàm Join trong VBA. Help nói với tôi:
Mã:
Join Function
Description
Returns a string created by joining a number of [B][COLOR=#ff0000]substrings contained in an array[/COLOR][/B].
Syntax
Join(sourcearray[, delimiter])
The Join function syntax has these named arguments:
Part Description
sourcearray Required. One-dimensional [B][COLOR=#ff0000]array containing substrings[/COLOR][/B] to be joined.
delimiter Optional. String character used to separate the substrings in the returned string. If omitted, the space character (" ") is used. If delimiter is a zero-length string (""), all items in the list are concatenated with no delimiters.
Trường hợp xanh là dạng số cũng trả kết quả là 1 và trường hợp đỏ là dạng số trong chuỗi, cũng trả kết quả là 1
Thêm nữa, 2 trường hợp mảng này, một dạng là số và một dạng số trong dạng chuỗi thì hàm Join vẫn cho ra kết quả:
Mã:
Sub Macro1()
Dim Arr()
Arr = Array(1, 2, 3, 4, 5)
MsgBox Join(Arr, ", ")
End Sub
Sub Macro2()
Dim Arr()
Arr = Array("1", "2", "3", "4", "5")
MsgBox Join(Arr, ", ")
End Sub
vậy cái Substring (chuỗi con) đâu nhất thiết phải là một chuỗi hay một số?
Và thủ tục này, i là biến kiểu Byte, nếu một biến X nào đó (không để kiểu) mà bằng i (X = i) thì X chính là dạng Byte, điều này ai cũng biết. Thế thì:
Mã:
Sub Test()
Dim i As Byte
Dim Arr(0 To 9)
For i = 0 To 9
Arr(i) = i
Next
[B][COLOR=#0000ff] MsgBox TypeName(Arr(1))[/COLOR][/B]
[COLOR=#ff0000][B] MsgBox Join(Arr, ",")[/B][/COLOR]
End Sub
Nhưng Byte thì Byte hàm JOIN vẫn "xơi" tuốt luốt, không bị "mắc xương"!
Thêm nữa, trong HELP không nói rằng "không được dùng mảng một chiều có dạng Numeric Data Type". Chính vì điều này mới đố chứ (he he) và đương nhiên nếu nó đưa câu đó ra thì ai đố làm gì vì ai cũng có thể xem Help!
Nếu bạn muốn tranh luận với tôi thì nên dùng từ chính xác.
Thêm nữa, 2 trường hợp mảng này, một dạng là số và một dạng số trong dạng chuỗi thì hàm Join vẫn cho ra kết quả:
Mã:
Sub Macro1()
Dim Arr()
Arr = Array(1, 2, 3, 4, 5)
MsgBox Join(Arr, ", ")
End Sub
Sub Macro2()
Dim Arr()
Arr = Array("1", "2", "3", "4", "5")
MsgBox Join(Arr, ", ")
End Sub
vậy cái Substring (chuỗi con) đâu nhất thiết phải là một chuỗi hay một số?
Sub Macro1()
Dim Arr()
Arr = Array(1, 2, 3, 4, 5)
MsgBox Arr(0)
MsgBox TypeName(Arr(0))
Arr(0) = "hic hic"
MsgBox Arr(0)
MsgBox TypeName(Arr(0))
End Sub
thì không có lỗi nào. Đơn giản vì mỗi phần tử của Arr là VARIANT
Nhưng chạy
Mã:
Sub Macro2()
Dim Arr(0 To 4) As Long, index As Long
For index = 0 To 4
Arr(index) = index + 1
Next
MsgBox Arr(0)
MsgBox TypeName(Arr(0))
Arr(0) = "hic hic" ' <-- lỗi
MsgBox Arr(0)
MsgBox TypeName(Arr(0))
End Sub
Sub Macro2()
Dim Arr(0 To 4) As Long, index As Long
For index = 0 To 4
Arr(index) = index + 1
Next
MsgBox Arr(0)
MsgBox TypeName(Arr(0))
Arr(0) = "7689"
MsgBox Arr(0)
MsgBox TypeName(Arr(0))
MsgBox TypeName(Arr(1))
End Sub
Đơn giản vì VBA theo tôi quá thân thiện. Bạn có thể nhập STRING nhưng phải ở dạng có thể convert được sang LONG. Lúc đó cái bạn nhập vào sẽ được convert sang LONG nếu cần thiết (nhập Long thì không cần thiết convert, nhập string thì cần) và kiểu của nó là LONG dù bạn đã nhập vào chuỗi.
Và thủ tục này, i là biến kiểu Byte, nếu một biến X nào đó (không để kiểu) mà bằng i (X = i) thì X chính là dạng Byte, điều này ai cũng biết. Thế thì:
Mã:
Sub Test()
Dim i As Byte
Dim Arr(0 To 9)
For i = 0 To 9
Arr(i) = i
Next
[B][COLOR=#0000ff] MsgBox TypeName(Arr(1))[/COLOR][/B]
[COLOR=#ff0000][B] MsgBox Join(Arr, ",")[/B][/COLOR]
End Sub
Nhưng Byte thì Byte hàm JOIN vẫn "xơi" tuốt luốt, không bị "mắc xương"!
Thêm nữa, trong HELP không nói rằng "không được dùng mảng một chiều có dạng Numeric Data Type". Chính vì điều này mới đố chứ (he he) và đương nhiên nếu nó đưa câu đó ra thì ai đố làm gì vì ai cũng có thể xem Help!
Nhưng thực chất đây là một số trong dạng chuỗi (Number stored as text) >> không tính toán được với số này! Thế thôi!
Mã:
Sub Macro2()
Dim Arr(0 To 4) As Long, index As Long
For index = 0 To 4
Arr(index) = index + 1
Next
MsgBox Arr(0)
MsgBox TypeName(Arr(0))
Arr(0) = "hic hic" ' <-- lô~i
MsgBox Arr(0)
MsgBox TypeName(Arr(0))
End Sub
Vì dạng Long không chấp nhận chuỗi nên phát sinh ra lỗi. OK
Nhưng với VD dưới đây, kiểu số nó ngán kiểu chuỗi chứ thằng kiểu chuỗi nó đâu có ngán kiểu số:
Mã:
Sub test3()
Dim Arr(0 To 3) [COLOR=#ff0000][B]As String[/B][/COLOR]
Arr(0) = "Hoang"
Arr(1) = "Trong"
Arr(2) = "Nghia"
[COLOR=#ff0000][B] Arr(3) = 2151[/B][/COLOR]
MsgBox Arr(3)
End Sub
Vì thế, JOIN là hàm xử lý ghép các phần tử trong mảng ra chuỗi (số cũng sẽ cho ra chuỗi, mà chuỗi đương nhiên là chuỗi), thì tại sao phải vấp lỗi khi gặp mảng dữ liệu kiểu số?
À, mình làm kiểu "lụi" đó mà, sau khi lấy nguồn từ Unicode cho ComboBox, CBB này đã được cài sẳn Script Vietnamese, sau đó chọn một mục trong list, rồi click vào một cell nào đó. Trong quá trình này mình chạy sự kiện LostFocus để chuyển chuỗi của CBB từ chuỗi Uni sang chuỗi CP1258. Kiểu "lụi" này không thành công.
Còn file của nmhung49 là đã tạo cái source ngay từ đầu rồi.
Nhưng thực chất đây là một số trong dạng chuỗi (Number stored as text) >> không tính toán được với số này! Thế thôi!
Vì dạng Long không chấp nhận chuỗi nên phát sinh ra lỗi. OK
Nhưng với VD dưới đây, kiểu số nó ngán kiểu chuỗi chứ thằng kiểu chuỗi nó đâu có ngán kiểu số:
Mã:
Sub test3()
Dim Arr(0 To 3) [COLOR=#ff0000][B]As String[/B][/COLOR]
Arr(0) = "Hoang"
Arr(1) = "Trong"
Arr(2) = "Nghia"
[COLOR=#ff0000][B] Arr(3) = 2151[/B][/COLOR]
MsgBox Arr(3)
End Sub
Vì thế, JOIN là hàm xử lý ghép các phần tử trong mảng ra chuỗi (số cũng sẽ cho ra chuỗi, mà chuỗi đương nhiên là chuỗi), thì tại sao phải vấp lỗi khi gặp mảng dữ liệu kiểu số?
Hùa thêm với bác Siwtom một chút nữa, theo phần tài liệu hoá của Microsoft về hàm Join tại trang dưới đây, họ viết khá rõ về tham số đầu vào cho hàm Join.
[GPECODE=csharp]Function Join(
ByVal SourceArray() As { Object | String },
Optional ByVal Delimiter As String = " "
) As String[/GPECODE] http://msdn.microsoft.com/en-us/library/b65z3h4h(v=vs.90).aspx
Ngoài ra, còn một điểm khác, khi không đặc tả kiểu số liệu của tham số thì hàm Join sẽ sử dụng tính năng chuyển đổi kiểu dữ liệu thụ động (implicit conversion) đưa số liệu về kiểu ký tự. Xem liên kết.
[http://msdn.microsoft.com/en-us/library/kca3w8x6.aspx]
Nếu đặc tả kiểu dữ liệu đầu vào thì nó sẽ sử dụng kiểu dữ liệu đó, không chuyển đổi kiểu số liệu nữa và xử lý, nếu xử lý không được thì nó sẽ báo lỗi về sai cấu trúc dữ liệu. Tất nhiên nếu khai báo kiểu số liệu là Variant thì implicit conversion sẽ được kích hoạt để đưa tham số đầu vào về khuôn khổ.
Xin được võ vẽ thêm đôi câu cùng Nghĩa!
Ví dụ mình sửa lại một tí đoạn test của Nghĩa để thí nghiệm vụ Implicit và Explicit...
[GPECODE=vb]Sub testJoin() 'Tat ca bien kieu Numeric deu bi loi:
Dim i As Long
Dim Arr1() As Date ' Integer, Byte, Double, Currency, Date v.v...
Dim Arr() As Variant
ReDim Arr1(9)
ReDim Arr(9)
For i = 0 To 9
Arr1(i) = i
Arr(i) = Arr1(i)
Next
Debug.Print Join(Arr, ",")
End Sub[/GPECODE]