Găp vấn đề Frame trong VBA (1 người xem)

Liên hệ QC

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

phihndhsp

Thành viên gạo cội
Tham gia
26/12/09
Bài viết
3,363
Được thích
2,488
Giới tính
Nam
Nghề nghiệp
Giáo Viên
Picture1.jpg
tôi muốn thiết kế 1 option chọn lựa một trong nhiều giống như trong VB
nhưng sau khi tôi thiết kế xong và thoát khỏi chế độ design thì nó lại ẩn các option đi, có cách nào khi cho nó chạy thì nó vẫn hiện như dạng thiết kế không? xin cảm ơn
 
View attachment 76850
tôi muốn thiết kế 1 option chọn lựa một trong nhiều giống như trong VB
nhưng sau khi tôi thiết kế xong và thoát khỏi chế độ design thì nó lại ẩn các option đi, có cách nào khi cho nó chạy thì nó vẫn hiện như dạng thiết kế không? xin cảm ơn
Bây giờ bạn kéo các Option button ra ngoài Frame, sau đó kéo thả vào lại trong Frame. Thêm nữa, kiểm tra lại thuộc tính Visible của chúng.
Nếu vẫn không được thì bạn thử làm lại:
- Vẽ Frame trước.
- Vẽ các Option button vào trong Frame.
 
Vẫn không được anh nghĩa phúc, khi thoát khỏi chế độ design thì nó mất những option đi, không giống như trong VB(trong VB vẽ tự do và không bao giờ bị lỗi như vậy)
 
Vẫn không được anh nghĩa phúc, khi thoát khỏi chế độ design thì nó mất những option đi, không giống như trong VB(trong VB vẽ tự do và không bao giờ bị lỗi như vậy)

Chưa hình dung được vấn đề... bạn thử đưa file lên đây xem thế nào nhé
 
Anh xem dùm em nha, sau khi thiết kế xong, em thoát chế độ Design thì những nút option ẩn mất, hôm trước hứa gởi anh kg khô mực mà không thấy anh hồi âm gì nên em về quê rồi, có dịp em gởi anh khô nướng nhậu rai rai
 

File đính kèm

Dùng Groupbox và option button của Form controls sẽ không bị tình trạng đó.
 
Lần chỉnh sửa cuối:
Đúng là dùng Form Control hay hơn ActiveX Control (truờng hợp này), lỗi này cũng khó hiểu, giống trường hợp bản Form 2 (hỗ trợ Unicode) của VB6.
 
Anh xem dùm em nha, sau khi thiết kế xong, em thoát chế độ Design thì những nút option ẩn mất, hôm trước hứa gởi anh kg khô mực mà không thấy anh hồi âm gì nên em về quê rồi, có dịp em gởi anh khô nướng nhậu rai rai
Thì ra là vậy!
Nói chung, MS đã thiết kế thì phải có cách xài
Hướng dẫn bạn cách dùng Frame trên Sheet như sau:
1> Vẽ 1 Frame lên sheet
2> Click phải vào Frame, chọn Frame Object\Edit ---> 1 Toolbox sẽ hiện ra
3> Nếu không thấy Toolbox xuất hiện, hãy click phải vào Frame lần nữa và chọn Toolbox
4> Trên cửa sổ Toolbox, chọn OptionButton rồi vẽ vào Frame
5> Thoát chế độ Design Mode
Xong!
Lưu ý: Sau này, nếu muốn xóa OptionButton trên Frame, hãy làm lại bước 2
 

File đính kèm

Lần chỉnh sửa cuối:
dạ em làm được rồi, cảm ơn anh nhiều, chúc anh và gd năm mới vui vẻ
 
Sao Việt code cho các đối tượng không được anh oi
 
A có thể tạo cho em một file mẫu gồm
Combo box, list box, command
Sao em tạo được rồi mà không biết vào chỗ nào để viết code và viết trên class cũng biết cách khai báo và gọi các đối tượng trên frame
Nhờ a giúp em
 
A có thể tạo cho em một file mẫu gồm
Combo box, list box, command
Sao em tạo được rồi mà không biết vào chỗ nào để viết code và viết trên class cũng biết cách khai báo và gọi các đối tượng trên frame
Nhờ a giúp em

Làm cho bạn 1 Frame gồm có: 1 ComboBox, 1 ListBox, 1 TextBox và 1 CommandButton
Toàn bộ code:
Mã:
[COLOR=#ff0000]Public WithEvents cboBox As MSForms.ComboBox
Public WithEvents lstBox As MSForms.ListBox
Public WithEvents cmdBtt As MSForms.CommandButton
Public WithEvents txtBox As MSForms.TextBox[/COLOR]
[COLOR=#0000cd]Private Sub Frame1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  If TypeName(cboBox) = "Nothing" Then
    Set cboBox = Sheet1.Frame1.Controls("ComboBox1")
    cboBox.List() = Sheet1.Range("A1:A10").Value
  End If
  If TypeName(lstBox) = "Nothing" Then
    Set lstBox = Sheet1.Frame1.Controls("ListBox1")
    lstBox.List() = Sheet1.Range("A1:A10").Value
  End If
  If TypeName(cmdBtt) = "Nothing" Then
    Set cmdBtt = Sheet1.Frame1.Controls("CommandButton1")
  End If
End Sub[/COLOR]
Private Sub cboBox_Click()
  Sheet1.Frame1.Controls("TextBox1").Text = cboBox.Text
End Sub
Private Sub lstBox_Click()
  Sheet1.Frame1.Controls("TextBox1").Text = lstBox.Text
End Sub
Private Sub cmdBtt_Click()
  MsgBox "Hello!"
End Sub
4 dòng khai báo màu đỏ để khai báo các controls (bắt buộc phải có)
Các dòng code màu xanh dùng sự kiện MouseMove trên Frame để khởi tạo các controls (cũng bắt buộc phải có nhưng có thể dùng các sự kiện khác như Auto_Open, Worksheet_Activate... để khởi tạo)
Các dòng code dưới cùng: Điều khiển sự kiện các control (viết bình thường như trên form)
-------------------
Quy trinh là vậy, bạn có thể tùy biến thêm
 

File đính kèm

Cắm ơn anh, em tạo được rồi, nhưng còn 2 vấn đề em chưa biết viết như thế nào.
- Em muốn chọn cùng lúc nhiều dòng trong list và chọn nút chọn sẽ tự động lấy vào excell những dòng vừa chọn
- Trong combo danh sách có nhiều mã hàng úa em muốn nhập nội dung trên cell hiện tại sẽ tự động lọc các dòng có nọi dung mình muốn tìm@!##
 
Lập một cái class module (WithEvents) attach sự kiện click vào các opt buttons lúc init form
Tuy nhiên, ở đây chỉ có 4 buttons cho nên code sẽ cực hơn code click event cho từng button.
 
View attachment 184385
Xin cho hỏi, tôi có một vấn đề như sau
trong form tôi có 1 frame và các option, tôi muốn chọn option nào thì sẽ hiển thị thông báo option đó
nếu sự kiện thông qua nút command button để click thì không nói gì, còn nếu cài đặt sự kiện cho từng option thì quá cực
có cách nào cài đặt sự kiện trong frame để khi click vào từng option thì hiện ra thông báo như trên không?
xin chân thành cảm ơn
Cũng lại dùng Class thôi. Cách làm như sau:
1> Vào menu Insert, chọn Class Module (để chèn 1 class). Mặc định có tên là "Class1".
Code trong class:
Mã:
Public WithEvents opt As MSForms.OptionButton
Private Sub opt_Click()
  MsgBox opt.Name
End Sub
2> Trong UserForm, bỏ hết code của bạn và chèn code dưới đây vào:
Mã:
Dim obj() As New Class1
Private Sub UserForm_Initialize()
  Dim ctr As Control, n As Long
  For Each ctr In Me.Frame1.Controls
    ReDim Preserve obj(n)
    Set obj(n).opt = ctr
    n = n + 1
  Next
End Sub
Lưu ý chỗ Dim obj() As New Class1 . Nếu trước đó, khi bạn chèn Class rồi đổi tên của nó thành "ABC" gì gì đó thì chỗ này bạn cũng phải sửa "Class1" thành "ABC" tương ứng
 
mong anh hướng dẫn cách làm việc với các đối tượng trong multipage ở trường hợp này với ạ, thanks

Public WithEvents cboBox As MSForms.ComboBox
Public WithEvents lstBox As MSForms.ListBox
Public WithEvents cmdBtt As MSForms.CommandButton
Public WithEvents txtBox As MSForms.TextBox

Private Sub Frame1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If TypeName(cboBox) = "Nothing" Then
Set cboBox = Sheet1.Frame1.Controls("ComboBox1")
cboBox.List() = Sheet1.Range("A1:A10").Value
End If
If TypeName(lstBox) = "Nothing" Then
Set lstBox = Sheet1.Frame1.Controls("ListBox1")
lstBox.List() = Sheet1.Range("A1:A10").Value
End If
If TypeName(cmdBtt) = "Nothing" Then
Set cmdBtt = Sheet1.Frame1.Controls("CommandButton1")
End If
End Sub

Private Sub cboBox_Click()
Sheet1.Frame1.Controls("TextBox1").Text = cboBox.Text
End Sub
Private Sub lstBox_Click()
Sheet1.Frame1.Controls("TextBox1").Text = lstBox.Text
End Sub
Private Sub cmdBtt_Click()
MsgBox "Hello!"
End Sub
 
mong anh hướng dẫn cách làm việc với các đối tượng trong multipage ở trường hợp này với ạ, thanks

Public WithEvents cboBox As MSForms.ComboBox
Public WithEvents lstBox As MSForms.ListBox
Public WithEvents cmdBtt As MSForms.CommandButton
Public WithEvents txtBox As MSForms.TextBox

Private Sub Frame1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If TypeName(cboBox) = "Nothing" Then
Set cboBox = Sheet1.Frame1.Controls("ComboBox1")
cboBox.List() = Sheet1.Range("A1:A10").Value
End If
If TypeName(lstBox) = "Nothing" Then
Set lstBox = Sheet1.Frame1.Controls("ListBox1")
lstBox.List() = Sheet1.Range("A1:A10").Value
End If
If TypeName(cmdBtt) = "Nothing" Then
Set cmdBtt = Sheet1.Frame1.Controls("CommandButton1")
End If
End Sub

Private Sub cboBox_Click()
Sheet1.Frame1.Controls("TextBox1").Text = cboBox.Text
End Sub
Private Sub lstBox_Click()
Sheet1.Frame1.Controls("TextBox1").Text = lstBox.Text
End Sub
Private Sub cmdBtt_Click()
MsgBox "Hello!"
End Sub
Hổng có file lấy gì mà kiểm tra đây bạn?
 
Làm cho bạn 1 Frame gồm có: 1 ComboBox, 1 ListBox, 1 TextBox và 1 CommandButton
Toàn bộ code:
Mã:
[COLOR=#ff0000]Public WithEvents cboBox As MSForms.ComboBox
Public WithEvents lstBox As MSForms.ListBox
Public WithEvents cmdBtt As MSForms.CommandButton
Public WithEvents txtBox As MSForms.TextBox[/COLOR]
[COLOR=#0000cd]Private Sub Frame1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  If TypeName(cboBox) = "Nothing" Then
    Set cboBox = Sheet1.Frame1.Controls("ComboBox1")
    cboBox.List() = Sheet1.Range("A1:A10").Value
  End If
  If TypeName(lstBox) = "Nothing" Then
    Set lstBox = Sheet1.Frame1.Controls("ListBox1")
    lstBox.List() = Sheet1.Range("A1:A10").Value
  End If
  If TypeName(cmdBtt) = "Nothing" Then
    Set cmdBtt = Sheet1.Frame1.Controls("CommandButton1")
  End If
End Sub[/COLOR]
Private Sub cboBox_Click()
  Sheet1.Frame1.Controls("TextBox1").Text = cboBox.Text
End Sub
Private Sub lstBox_Click()
  Sheet1.Frame1.Controls("TextBox1").Text = lstBox.Text
End Sub
Private Sub cmdBtt_Click()
  MsgBox "Hello!"
End Sub
4 dòng khai báo màu đỏ để khai báo các controls (bắt buộc phải có)
Các dòng code màu xanh dùng sự kiện MouseMove trên Frame để khởi tạo các controls (cũng bắt buộc phải có nhưng có thể dùng các sự kiện khác như Auto_Open, Worksheet_Activate... để khởi tạo)
Các dòng code dưới cùng: Điều khiển sự kiện các control (viết bình thường như trên form)
-------------------
Quy trinh là vậy, bạn có thể tùy biến thêm
dạ em copy nguyên trả lời của anh ở page1 ạ, giờ e muốn làm việc thêm với cái multipage mà chưa biết làm thế nào, google vẫn chưa ra ạ, mong anh giúp đỡ, thanks
 

File đính kèm

Em muốn khai báo khởi tạo sự kiện cho textbox, commandbutton trong multipage, giống như trên frame ở trên ấy ạ, thanks
 
Em muốn khai báo khởi tạo sự kiện cho textbox, commandbutton trong multipage, giống như trên frame ở trên ấy ạ, thanks
Thì khai báo bình thường thôi. Chẳng hạn muốn khai báo TextBox2 nằm trên Page2 của MultiPage thì:
Mã:
'...........................
Public WithEvents txtBox2 As MSForms.TextBox
Private Sub Frame1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  '..............................
  If TypeName(txtBox2) = "Nothing" Then
    Set txtBox2 = Sheet1.Frame1.Controls("MultiPage1").Pages(1).Controls("TextBox2")
  End If
End Sub
Rồi code sự kiện cũng bình thường luôn, chẳng hạn:
Mã:
Private Sub txtBox2_Change()
  Sheet1.Frame1.Controls("MultiPage1").Pages(1).Controls("Label2").Caption = txtBox2.Text
End Sub
Quan trọng là chỉ rõ "nó nằm ở chỗ nào"
 
anh cho em hỏi thêm là: em có cái button ở ngoài frame, giờ muốn tương tác với cái multipage, em viết như này ko đúng, ko biết lại sửa lại như nào, mong anh chỉ giúp, thanks!

Private Sub reportBtt_Click()
If Sheet1.Frame1.Controls("MultiPage1").Pages(0).Select = True Then Msgbox txtBox1.Value
If Sheet1.Frame1.Controls("MultiPage1").Pages(1).Select = True Then Msgbox txtBox2.Value
End Sub
 
anh cho em hỏi thêm là: em có cái button ở ngoài frame, giờ muốn tương tác với cái multipage, em viết như này ko đúng, ko biết lại sửa lại như nào, mong anh chỉ giúp, thanks!

Private Sub reportBtt_Click()
If Sheet1.Frame1.Controls("MultiPage1").Pages(0).Select = True Then Msgbox txtBox1.Value
If Sheet1.Frame1.Controls("MultiPage1").Pages(1).Select = True Then Msgbox txtBox2.Value
End Sub
e đã làm được rồi, thanks anh,

Select Case Sheet1.Frame1.Controls("MultiPage1").Value
Case 0
MsgBox "page1"
Case 1
MsgBox "page2"
Case 2
MsgBox "page3"
End Select
 
Anh cho em hỏi sự khác nhau trong câu lệnh trên khi dùng "New" và không dùng "New".
Theo kinh nghiệm thì tôi thấy rằng: Nếu có "New" thì khỏi phải có động tác "Set"
Ví dụ:
Mã:
Sub Test1()
  Dim x As New Application
  MsgBox x.Sum(3, 2)
End Sub
Nhưng không có "New" thì phải "Set"
Mã:
Sub Test2()
  Dim x As Application
  Set x = New Application
  MsgBox x.Sum(3, 2)
End Sub
Ví dụ khác có "New" thì khỏi "Set"
Mã:
Sub Test1()
  Dim x As New Collection
  x.Add 1, "1"
  x.Add 2, "2"
  x.Add 3, "3"
  MsgBox x.Count
End Sub
Và không có "New" thì phải "Set"
Mã:
Sub Test2()
  Dim x As Collection
  Set x = New Collection
  x.Add 1, "1"
  x.Add 2, "2"
  x.Add 3, "3"
  MsgBox x.Count
End Sub
Nghe "thiên hạ đồn" rằng Nên dùng "Dim + Set" thì sẽ tốt hơn là "Dim New"
Chẳng biết nữa, thấy cái nào chạy thì mình dùng, cũng chưa đủ trình độ để biết cái nào tốt hơn
 
Trước mình thử bỏ new, hình như ko chạy,
Cái này để gọi cái funtion ở trong class1 ra
Bình thường nó là một mảng ojbject ( class1)
Theo kinh nghiệm thì tôi thấy rằng: Nếu có "New" thì khỏi phải có động tác "Set"
Ví dụ:
Mã:
Sub Test1()
  Dim x As New Application
  MsgBox x.Sum(3, 2)
End Sub
Nhưng không có "New" thì phải "Set"
Mã:
Sub Test2()
  Dim x As Application
  Set x = New Application
  MsgBox x.Sum(3, 2)
End Sub
Ví dụ khác có "New" thì khỏi "Set"
Mã:
Sub Test1()
  Dim x As New Collection
  x.Add 1, "1"
  x.Add 2, "2"
  x.Add 3, "3"
  MsgBox x.Count
End Sub
Và không có "New" thì phải "Set"
Mã:
Sub Test2()
  Dim x As Collection
  Set x = New Collection
  x.Add 1, "1"
  x.Add 2, "2"
  x.Add 3, "3"
  MsgBox x.Count
End Sub
Nghe "thiên hạ đồn" rằng Nên dùng "Dim + Set" thì sẽ tốt hơn là "Dim New"
Chẳng biết nữa, thấy cái nào chạy thì mình dùng, cũng chưa đủ trình độ để biết cái nào tốt hơn


Nếu chỉ là object thì em hiều, em hỏi là cái chuyện dùng toán tử new khi khai báo một mảng động đối tượng cơ.

dim obj() as new class1
 
Bình thường nó là một mảng ojbject ( class1)



Nếu chỉ là object thì em hiều, em hỏi là cái chuyện dùng toán tử new khi khai báo một mảng động đối tượng cơ.

dim obj() as new class1
Thấy cũng vậy thôi mà. Bạn có quyền Dim obj() as Class1 cũng đâu có vấn đề gì. Chỉ là sau đó bạn phải có động tác Set obj() = New Class1
Ví dụ: Tôi có 1 Form gồm 10 CommandButton (nằm trong 1 Frame). Thông thường tôi viết code như sau:
1> Code trong class (tên class là clsEvent)
Mã:
Public WithEvents cmdBtt As MSForms.CommandButton
Private Sub cmdBtt_Click()
  MsgBox "Xin chào! Tôi là '" & cmdBtt.Caption & "'"
End Sub
2> Code trong form
Mã:
Dim btt() As New clsEvent
Private Sub UserForm_Initialize()
  Dim n As Long, ctr As Control
  For Each ctr In Me.Frame1.Controls
    n = n + 1
    ReDim Preserve btt(1 To n)
    Set btt(n).cmdBtt = ctr
  Next
End Sub
----------------------------------------
Tuy nhiên, nếu không thích Dim.. New thì tôi cũng có thể viết khác:
Mã:
Dim btt() As clsEvent
Private Sub UserForm_Initialize()
  Dim n As Long, ctr As Control
  For Each ctr In Me.Frame1.Controls
    n = n + 1
    ReDim Preserve btt(1 To n)
    Set btt(n) = New clsEvent
    Set btt(n).cmdBtt = ctr
  Next
End Sub
Tóm lại: Không Dim... as New... thì phải có động tác Set... = New....
Đường nào cũng phải.. tới bến và.. ói thôi!
 

File đính kèm

Nhân tiện nói về cái vụ Dim.. as New, tôi có 1 thí nghiệm khá thú vị:
Mã:
Sub Test1()
  Dim app As New Application
  Set app = Nothing
  MsgBox app Is Nothing
End Sub
Sub Test2()
  Dim app As Application
  Set app = New Application
  Set app = Nothing
  MsgBox app Is Nothing
End Sub
Mời chạy 2 sub trên và cho biết ý kiến
Xin hỏi với Sub Test1, tôi phải giải phóng đối tượng app bằng cách nào?
Trong thời gian chờ đợi câu trả lời thì tôi cảm thấy... "hình như là" cách viết theo kiểu Sub Test2 sẽ "an toàn" hơn thì phải
(không biết nữa, chỉ đoán)
 
Code đầu hiện là True, code thứ 2 hiện là False. Khi khai báo với toán tử New thì thực sự đối tượng chưa hề được tạo, chỉ khi ta truy cập mà nó đang là nothing thì nó mới chính thức được tạo. Ngược lại khi không có toàn tử New thì người dùng phải tự tạo đối tượng và gán cho nó. Còn về việc giải phóng thì chắc cứ gán nó thành nothing bình thương, nhưng không truy cập nó nữa là được.

hình như là" cách viết theo kiểu Sub Test2 sẽ "an toàn" hơn thì phả

Cách viết dùng New sẽ nguy hiểm hơn, nhiều khi muốn nó về nothing mà nó không chịu về.
 
Cách viết dùng New sẽ nguy hiểm hơn, nhiều khi muốn nó về nothing mà nó không chịu về.
Tôi cũng nghĩ vậy (mới nghĩ)... Trước giờ dùng nhưng không để ý, hôm nay bạn hỏi tự dưng thí nghiệm mới phát hiện vấn đề. Chắc phải thay đổi cách viết code từ đây
Cảm ơn bạn!
 
Code đầu hiện là True, code thứ 2 hiện là False. Khi khai báo với toán tử New thì thực sự đối tượng chưa hề được tạo, chỉ khi ta truy cập mà nó đang là nothing thì nó mới chính thức được tạo. Ngược lại khi không có toàn tử New thì người dùng phải tự tạo đối tượng và gán cho nó. Còn về việc giải phóng thì chắc cứ gán nó thành nothing bình thương, nhưng không truy cập nó nữa là được.
Chíng xác.

Có sự khác nhau giữa (New + không có Set) và không có (New + Set)
1.
Mã:
Dim obj As myClass   ' Object
Set obj = New myClass
Set obj = Nothing
MsgBox obj Is Nothing
Với code như thế thì VBA không tạo đối tượng mà bạn phải tự lực hết. Khi nào cần dùng obj thì phải tạo - Set obj = New myClass ' = CreateObject(...)
Khi nào thích thì giải phóng - Set obj = Nothing. Nếu trong đoạn code tiếp theo muốn dùng nữa thì lại phải tự tạo - Set obj = ...
2.
Mã:
Dim obj As New myClass
Set obj = Nothing
MsgBox obj Is Nothing
Thực ra dòng Set obj = Nothing là thừa vì ở thời điểm đó thì obj = Nothing. Vì sao? Ta xét code khác.
Mã:
Dim obj As New myClass
obj.HoTen = "Nguyen Van A"   ' (A)
Set obj = Nothing       ' (B)
MsgBox obj Is Nothing   ' (C)
Khi ta khai báo biến obj thì chả có đối tượng nào được tạo. Nhưng đã khai báo thế thì VBA vào cuộc. Tức ở mọi chỗ, không chỉ riêng chỗ đầu tiên, khi code truy cập tới đối tượng obj thì VBA sẽ kiểm tra xem obj có <> Nothing không. Nếu obj = Nothing thì VBA sẽ tạo đối tượng trước khi thực hiện câu lệnh của code.
Trước khi thực hiện câu lệnh (A) VBA kiểm tra thấy obj = Nothing, vậy nên nó tạo đối tượng, tiếp theo câu lệnh (A) được thực hiện trên đối tượng vừa tạo. Câu lệnh (B) chắc chắn 300% hủy đối tượng obj. Nhưng trước khi thực hiện câu lệnh (C) thì VBA lại kiểm tra thấy là obj = Nothing nên nó lại tạo đối tượng rồi sau đó mới thực hiện (C) trên đối tượng vừa tạo.
Không phải là (B) không hủy đối tượng. Chắc chắn (B) hủy đối tượng, nhưng "obj Is Nothing" = False bởi vì VBA trước khi thực hiện (C) đã kiểm tra thấy obj = Nothing nên tạo đối tượng, sau đó mới thực hiện (C). Rõ ràng khi thực hiện (C) thì obj <> Nothing.


Cách viết dùng New sẽ nguy hiểm hơn, nhiều khi muốn nó về nothing mà nó không chịu về.
Nó ngoan ngoãn về Nothing. Chỉ có điều sau khi Set ... = Nothing thì đừng truy cập tới nó nữa. Bởi nếu truy cập thì VBA lại tạo lại đối tượng, do đang là Nothing, để có thể thực hiện câu lệnh.
 
Chỉ có điều sau khi Set ... = Nothing thì đừng truy cập tới nó nữa

Đúng là làm vậy thì đối tượng sẽ được giải phóng.
Rắc rối ở chỗ là nếu áp dụng cho trường hợp mảng object, khi em muốn thiết lập một phần tử của mảng về nothing để thông báo rằng phần tử này không có giá trị dùng nữa, và trong tương lại nó có thể được tái dùng lại. Đến lúc muốn dùng lại thì đương nhiên em phải check xem phần tử đó có đang nothing không, lúc đó nó sẽ thành sai.
 
Chính vì thế nên bỏ dùng New trong khai báo. Lúc đó ta làm chủ được tình hình. Lúc nào cần thì tạo lúc nào thích thì hủy. Ông VBA không còn tự ý làm sau lưng ta nữa.
 
Sự khác nhau giữa (1) và (2) thì liên hệ như nào với khái niệm kiểu khai báo kết nối sớm (1') và kiểu khai báo kết nối trễ (2') hả anh?
Theo tôi thì thế này ... Theo tôi bởi tôi không dám cho là mình chắc chắn đúng.
New và khai báo sớm / trễ là hai vấn đề khác nhau.
A. Ta thêm reference "Microsoft Scripting Runtime"
Mã:
Sub test1()
Dim dic As New Scripting.Dictionary
    dic.Add "a", 1
    MsgBox dic.Item("a")
    Set dic = Nothing
    MsgBox dic Is Nothing
End Sub

Sub test2()
Dim dic As Scripting.Dictionary
    Set dic = CreateObject("Scripting.Dictionary")  '   hoac New Scripting.Dictionary
    dic.Add "a", 1
    MsgBox dic.Item("a")
    Set dic = Nothing
    MsgBox dic Is Nothing
End Sub
Theo tôi cả 2 trường hợp là kết nối sớm. Kiểu đối tượng được xác định cụ thể, rõ ràng. Kết nối sớm mặc dù trong trường hợp 1 có Dim dic As New Scripting.Dictionary, trường hợp sau không có New. Tức kết nối sớm ở đây chả liên quan gì tới New hay không có New.
Nếu có New thì tại mọi chỗ có truy cập tới dic (vd. dùng phương thức, thuộc tính của đối tượng) thì VBA sẽ kiểm tra dic, nếu = Nothing thì đối tượng sẽ được tạo trước khi thực hiện câu lệnh. Trong trường hợp 2 ta phải tự tạo đối tượng bằng Set dic = ...

B. Không thêm reference "Microsoft Scripting Runtime"
Rõ ràng trong trường hợp này không thể khai báo
Mã:
Dim dic As New Scripting.Dictionary
...
Dim dic As Scripting.Dictionary
Chỉ có thể
Mã:
Sub test3()
Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")
    dic.Add "Nguyen van A", 47
    MsgBox dic.Item("Nguyen van A")
    Set dic = Nothing
End Sub
Không thể Set dic = New ... Vì New gì?
Trong trường hợp này kiểu đối tượng không được xác định. Chỉ tới khi tạo ra đối tượng mới biết nó là kiểu gì. Trong trường hợp này theo tôi là kết nối trễ.

Và thực chất với khai báo Dim dic As Object thì trong nhiều chỗ của code có thể tạo ra nhiều kiểu đối tượng khác nhau và dùng chúng.
Mã:
Sub test4()
Dim obj As Object
    Set obj = CreateObject("Scripting.Dictionary")
    obj.Add "Nguyen van A", 47
    MsgBox obj.Item("Nguyen van A")
    Set obj = Nothing
    Set obj = CreateObject("Scripting.FileSystemObject")
    MsgBox obj.FileExists("C:\test.txt")
    Set obj = Nothing
End Sub
Trong trường hợp A kiểu đối tượng là rõ ràng, có tạo nhiều đối tượng thì luôn phải có kiểu cụ thể đó.
 
Nếu mà nhắc tới cái chuyện đối tượng tự động được tạo thì không thể không nhắc tới userform. Khi ta unload userform rồi, mà đột ngột ta gọi userform.show thì form lại tự động sống lại ( sự kiện khởi tạo xảy ra). Vậy cho em hỏi hai cái cơ chế này có liên quan gì tới nhau không?
 
Khi ta unload userform rồi, mà đột ngột ta gọi userform.show thì form lại tự động sống lại
Nếu bạn lấy ví dụ khác thì còn có lý, vì Show là hiển nhiên. Tại sao? Nếu UserForm1 đang chưa được Load mà không thể gọi Show để Load nó và Show nó thì bạn làm sao dùng được UserForm1? Bởi khi mở tập tin thì UserForm1 chưa được Load mà. Vậy bạn làm cách nào hiển thị UserForm1 để làm việc với nó? Thời điểm khi mở tập tin và thời điểm khi UserForm1 được hiển thị và sau đó Unload là hoàn toàn như nhau.

Nhiệm vụ của Show là Load UserForm1 nếu chưa được Load, rồi Show mà. Vậy thì tại sao lại ngạc nhiên khi có thể dùng Show để Load và Show UserForm1?
Vậy cho em hỏi hai cái cơ chế này có liên quan gì tới nhau không?
Tôi cũng không biết có cùng cơ chế hay không. Nếu bạn kiểm tra thấy đúng như thế thì bạn tự kết luận cho mình thôi.
 
"Nhiệm vụ của Show là Load UserForm1 nếu chưa được Load, rồi Show mà."


Nhưng phải thừa nhận một điều là show là một phương thức của userform1. Có nghĩa là phải có đối tượng, form phải được load thì mới gọi phương thức được. Em nghĩ show không hề load form mà do một bàn tay khác thực hiện, show chỉ làm nhiệm vụ hiển thị form. nothing-unload, new-userform, truy cập một thành viên gây ra quá trình tạo đối tượng. đó là mmotj vài từ khóa làm em liên tưởng chúng với nhau.
 
Em chỉ biết nói nôm na, dân dã vầy:
Trong VB cũ, VB.NET, thì form phải load và show, unload, set nothing khi không cần (referent count = 0 => destroy tường minh trong VB và destroy = cơ chế dọn rác của .NET framework)
Còn trong MSForms của VBA thì ông VBA lanh chanh, làm dùm luôn, luôn set referent count to object = 1 khi khởi tạo, tham chiếu, read properties... lần đầu tiên.
Bắt ổng unload form, set nothing thì ông lại set ref count =1, không chịu set = 0 nên không giải phóng được.
Các object khai báo, khởi tạo bằng Dim xxx as New XXX cũng theo cách này.
Các bạn thử kiểm chứng = cách debug với 1 userform
Code của form
Mã:
Option Explicit

Private Sub UserForm_Initialize()
    Debug.Print Me.Name & " initialize"
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Debug.Print Me.Name & " will be closed with Cancel = " & Str$(Cancel) & " and CloseMode = " & Str$(CloseMode)
End Sub

Private Sub UserForm_Terminate()
    Debug.Print Me.Name & " terminate"
End Sub
Code của macro:
Mã:
Option Explicit

Sub TestForm()
    Debug.Print "UserForm1 at address: 0x" & Hex$(VarPtr(UserForm1))
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
 
    UserForm1.Show

    Debug.Print "UserForm1 at address: 0x" & Hex$(VarPtr(UserForm1))
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
 
    Unload UserForm1
    Set UserForm1 = Nothing
 
    Debug.Print "UserForm1 at address: 0x" & Hex$(VarPtr(UserForm1))
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
End Sub
 
Lần chỉnh sửa cuối:
Có nghĩa là phải có đối tượng, form phải được load thì mới gọi phương thức được. Em nghĩ show không hề load form mà do một bàn tay khác thực hiện, show chỉ làm nhiệm vụ hiển thị form. nothing-unload, new-userform,
Nói chính xác thì khi Show được gọi mà UserForm chưa được Load thì VB sẽ Load nó. Cũng hiển nhiên thôi. Vì nếu không UserForm.Show được khi UserForm chưa được Load thì làm sao dùng UserForm được.

Ý 2 của tôi là những gì tự kiểm tra được thì cứ tự kiểm nghiệm, không cần phải hỏi làm gì.
 
Lần chỉnh sửa cuối:
Bắt ổng unload form, set nothing thì ông lại set ref count =1, không chịu set = 0 nên không giải phóng được.
Chắc chắn là giải phóng. Nhưng do sau đó lại truy cập tới UserForm1 nên VB lại tạo nó.
Dễ kiểm tra thôi.
Đặt Button1 trên Sheet1
Mã:
Sub Button1_Click()
    UserForm1.Show
End Sub
Thêm UserForm1 với 3 CommandButton
Mã:
Private Sub CommandButton1_Click()
    Unload Me
    UserForm1.Show
End Sub

Private Sub CommandButton2_Click()
    Me.Hide
End Sub

Private Sub CommandButton3_Click()
    Unload Me
    UserForm1.Width = 500
End Sub

Private Sub UserForm_Initialize()
    MsgBox "Initialize"
End Sub

Private Sub UserForm_Terminate()
    MsgBox "Terminate"
End Sub
1. Nhấn Button1. Ta thấy code UserForm_Initialize được thực hiện, tức VB tạo đối tượng. Tiếp theo nhấn CommandButton1. Ta thấy UserForm_Terminate được thực hiện (thủ phạm là Unload), tức đối tượng được giải phóng. Tiếp ngay theo UserForm_Initialize được thực hiện, tức VB lại tạo đối tượng - thủ phạm là gọi Show mà UserForm1 chưa được Load vào memory nên VB lại tạo đối tượng. Rõ ràng đối tượng thực sự được giải phóng. Nhưng được tạo lại do gọi Show. Nếu sau khi giải phóng mà không gọi Show thì đối tượng không được tạo. Bằng chứng là nếu bỏ UserForm1.Show thì sau khi nhấn Button1 thì UserForm_Initialize được thực hiện, tức chỉ tới thời điểm này UserForm1 mới được Load vào memory, đây là bằng chứng là sau Unload và trước khi nhấn Button1 thì trong memory không có UserForm1.
2. Nhấn Button1. Ta thấy code UserForm_Initialize được thực hiện, tức VB tạo đối tượng. Tiếp theo nhấn CommandButton2. Ta thấy UserForm_Terminate không được thực hiện, bởi UserForm1 chỉ bị ẩn nhưng vẫn tồn tại trong memory, không được giải phóng. Chính vì nó vẫn tồn tại nên nếu lúc này nhấn Button1 thì VB không tạo, ta thấy rõ ràng là UserForm_Initialize không được thực hiện. Show lúc này chẳng qua là hiển thị UserForm1 đang bị ẩn nhưng luôn tồn tại trong memory.
3. Nhấn Button1. Ta thấy code UserForm_Initialize được thực hiện, tức VB tạo đối tượng. Tiếp theo nhấn CommandButton3. Ta thấy UserForm_Terminate được thực hiện (thủ phạm là Unload), tức đối tượng được giải phóng. Tiếp ngay theo UserForm_Initialize được thực hiện, tức VB lại tạo đối tượng - thủ phạm là do truy cập tới UserForm1 (UserForm1.Width = 300). Nhưng VB chỉ tạo đối tượng mà không hiển thị nó. Nếu sau đó nhấn Button1 thì thấy đúng là chiều rộng của UserForm1 đã thay đổi.
 
Em chỉ biết nói nôm na, dân dã vầy:
Trong VB cũ, VB.NET, thì form phải load và show, unload, set nothing khi không cần (referent count = 0 => destroy tường minh trong VB và destroy = cơ chế dọn rác của .NET framework)
Còn trong MSForms của VBA thì ông VBA lanh chanh, làm dùm luôn, luôn set referent count to object = 1 khi khởi tạo, tham chiếu, read properties... lần đầu tiên.
Bắt ổng unload form, set nothing thì ông lại set ref count =1, không chịu set = 0 nên không giải phóng được.
Các object khai báo, khởi tạo bằng Dim xxx as New XXX cũng theo cách này.
Các bạn thử kiểm chứng = cách debug với 1 userform
Code của form
Mã:
Option Explicit

Private Sub UserForm_Initialize()
    Debug.Print Me.Name & " initialize"
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Debug.Print Me.Name & " will be closed with Cancel = " & Str$(Cancel) & " and CloseMode = " & Str$(CloseMode)
End Sub

Private Sub UserForm_Terminate()
    Debug.Print Me.Name & " terminate"
End Sub
Code của macro:
Mã:
Option Explicit

Sub TestForm()
    Debug.Print "UserForm1 at address: 0x" & Hex$(VarPtr(UserForm1))
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
 
    UserForm1.Show

    Debug.Print "UserForm1 at address: 0x" & Hex$(VarPtr(UserForm1))
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
 
    Unload UserForm1
    Set UserForm1 = Nothing
 
    Debug.Print "UserForm1 at address: 0x" & Hex$(VarPtr(UserForm1))
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
End Sub


Chạy code trên thấy các địa chỉ giống nhau. Tuy nhiên sau khi chạy một cái code sau thì em lại bắt đầu nghi ngờ.


Mã:
Sub TestClass()
    Dim UF As UserForm1
    Set UF = UserForm1
    Unload UserForm1
    UserForm1.Caption = "Meo mun dang yeu"
    MsgBox UF.Caption
End Sub

Nếu địa chỉ không đổi, form không được giải phóng, thì biến UF cũng không thay đổi gì cả. Nhưng khi chạy cái msgbox lại bị lỗi, phải chăng chỉ là sự trùng hợp khi cấp phát bộ nhớ.

VÀ khi chạy thêm code sau thì càng hỏa mù luồn ( để cho đơn giản thì xóa hết code trong userform đi)

Mã:
Sub TestForm()
    Dim k As UserForm1, a As UserForm1
  
    Unload UserForm1
    Set k = UserForm1
    Debug.Print "Dia chi cua k la: " & Hex(ObjPtr(k))
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
  
    Unload UserForm1
    Set a = UserForm1
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
  
    Unload UserForm1
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
    Unload UserForm1
    Debug.Print "UserForm1 pointer to address: 0x" & Hex$(ObjPtr(UserForm1))
  
End Sub

Địa chỉ thay đổi tùm lum hết-->Địa chỉ không thay đổi chỉ là do một nguyên nhân đặc biệt nào đó mà thôi.
 
Lần chỉnh sửa cuối:
Chắc chắn là giải phóng. Nhưng do sau đó lại truy cập tới UserForm1 nên VB lại tạo nó.
Dễ kiểm tra thôi.
Đặt Button1 trên Sheet1
Mã:
Sub Button1_Click()
    UserForm1.Show
End Sub
Thêm UserForm1 với 3 CommandButton
Mã:
Private Sub CommandButton1_Click()
    Unload Me
    UserForm1.Show
End Sub

Private Sub CommandButton2_Click()
    Me.Hide
End Sub

Private Sub CommandButton3_Click()
    Unload Me
    UserForm1.Width = 500
End Sub

Private Sub UserForm_Initialize()
    MsgBox "Initialize"
End Sub

Private Sub UserForm_Terminate()
    MsgBox "Terminate"
End Sub
1. Nhấn Button1. Ta thấy code UserForm_Initialize được thực hiện, tức VB tạo đối tượng. Tiếp theo nhấn CommandButton1. Ta thấy UserForm_Terminate được thực hiện (thủ phạm là Unload), tức đối tượng được giải phóng. Tiếp ngay theo UserForm_Initialize được thực hiện, tức VB lại tạo đối tượng - thủ phạm là gọi Show mà UserForm1 chưa được Load vào memory nên VB lại tạo đối tượng. Rõ ràng đối tượng thực sự được giải phóng. Nhưng được tạo lại do gọi Show. Nếu sau khi giải phóng mà không gọi Show thì đối tượng không được tạo. Bằng chứng là nếu bỏ UserForm1.Show thì sau khi nhấn Button1 thì UserForm_Initialize được thực hiện, tức chỉ tới thời điểm này UserForm1 mới được Load vào memory, đây là bằng chứng là sau Unload và trước khi nhấn Button1 thì trong memory không có UserForm1.
2. Nhấn Button1. Ta thấy code UserForm_Initialize được thực hiện, tức VB tạo đối tượng. Tiếp theo nhấn CommandButton2. Ta thấy UserForm_Terminate không được thực hiện, bởi UserForm1 chỉ bị ẩn nhưng vẫn tồn tại trong memory, không được giải phóng. Chính vì nó vẫn tồn tại nên nếu lúc này nhấn Button1 thì VB không tạo, ta thấy rõ ràng là UserForm_Initialize không được thực hiện. Show lúc này chẳng qua là hiển thị UserForm1 đang bị ẩn nhưng luôn tồn tại trong memory.
3. Nhấn Button1. Ta thấy code UserForm_Initialize được thực hiện, tức VB tạo đối tượng. Tiếp theo nhấn CommandButton3. Ta thấy UserForm_Terminate được thực hiện (thủ phạm là Unload), tức đối tượng được giải phóng. Tiếp ngay theo UserForm_Initialize được thực hiện, tức VB lại tạo đối tượng - thủ phạm là do truy cập tới UserForm1 (UserForm1.Width = 300). Nhưng VB chỉ tạo đối tượng mà không hiển thị nó. Nếu sau đó nhấn Button1 thì thấy đúng là chiều rộng của UserForm1 đã thay đổi.
Giải phóng thì chắc chắn là đúng rồi anh. Chỉ là tại sao khi kiểm tra địa chỉ lại có lúc trùng, có lúc không trùng.
 
Chỉ là tại sao khi kiểm tra địa chỉ lại có lúc trùng, có lúc không trùng.
Không biết bạn kiểm tra gì, thế nào. Chỉ một câu mà không có code thì khó nói chuyện.

Đối tượng được tạo lần sau có thể nằm cùng chỗ với lần trước mà cũng có thể nằm chỗ khác. Nhất là được tạo lại sau một thời gian dài. Vì chỗ trống của lần trước trong khoảng thời gian qua đã được Manager Memory phân cho những dữ liệu khác, đối tượng khác. Không ai quan tâm tới vị trí "vật lý" trong memory, vì có thể mỗi lần là chỗ khác, không phải bất di bất dịch. Quan trọng là khi cần truy cập tới "chỗ ấy" thì biết phải dùng hàm gì để đọc ra vd. địa chỉ hiện thời.
 
Lần chỉnh sửa cuối:

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

Back
Top Bottom