UserForm: Hàm gì để gọi nó với tên dạng chuỗi? (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
Tôi luôn có thắc mắc như sau, các đối tượng đều có hàm gọi theo chuỗi tên hoặc trực tiếp.

VD:

Sheet1.Select

Sheets("Sheet1").Select

TextBox1.SetFocus

Controls("TextBox1").SetFocus

Call Macro

Run "Macro"

v.v...

Thế nhưng với UserForm thì tôi chưa biết nó gọi dạng chuỗi như thế nào.

UserForm1.Show

Nhưng không có UserForms("UserForm1").Show

Có thể là tôi chưa biết, nhưng nếu ai biết thì hướng dẫn cho tôi hàm nào gọi UserForm bằng dạng chuỗi.

Cám ơn rất nhiều.
 
Nghĩa ơi ở đây nè

http://www.cpearson.com/Excel/showanyform.htm


Chỉ cần như sau cũng được

Mã:
Sub Test()
Dim obj As Object
Set obj = VBA.UserForms.Add("[B][COLOR=#ff0000]Sea[/COLOR][/B]")
obj.Show vbModal
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Tôi luôn có thắc mắc như sau, các đối tượng đều có hàm gọi theo chuỗi tên hoặc trực tiếp.

VD:

Sheet1.Select

Sheets("Sheet1").Select

TextBox1.SetFocus

Controls("TextBox1").SetFocus

Call Macro

Run "Macro"

v.v...

Thế nhưng với UserForm thì tôi chưa biết nó gọi dạng chuỗi như thế nào.

UserForm1.Show

Nhưng không có UserForms("UserForm1").Show

Có thể là tôi chưa biết, nhưng nếu ai biết thì hướng dẫn cho tôi hàm nào gọi UserForm bằng dạng chuỗi.

Cám ơn rất nhiều.

Thường thì để "điều hành" một nhóm đối tượng cùng "loại" thì có một đố tượng "lãnh đạo" được tạo ra.
Ví dụ Control là một "loại" đối tượng. Để "điều hành" những đối tượng cùng loại này thì có đối tượng Controls. Những đối tượng "lãnh đạo" này là collection, và có những thuộc tính "chủ chốt" đặc trưng cho collection là: Add, Delete - Remove (dùng để thêm bớt đối tượng được điều hành vào tập), Item (dùng để truy cập tới đối tượng cụ thể trong tập), và Count (số lượng đối tượng trong tập)
Truy cập tới đối tượng cụ thể trong tập:
Mã:
(đối tượng lãnh đạo - collection).Item(index)

index là Variant xác định hoặc Tên hoặc Chỉ số trong tập của đối tượng cần truy cập vd. index = "Sheet1", index = 1

Vậy ta có thể truy cập tới Sheet1:
Mã:
Sheets.Item("Sheet1")
Sheets.Item(1)

Để rồi gọi phương thức của nó:
Mã:
Sheets.Item("Sheet1").Select
Sheets.Item(1).Select

Do trong collection thì Item là thuộc tính mặc định nên khi viết code ta có thể bỏ "Item" đi. Nếu ta không chỉ rõ là ta định truy cập tới thuộc tính nào thì "được hiểu ngầm" là ta định truy cập tới thuộc tính mặc định (không chỉ collection mà những đối tượng khác cũng có thuộc tính mặc định của mình)
Vậy ta có thể truy cập tới Sheet1:
Mã:
Sheets("Sheet1")
Sheets(1)

Để rồi gọi phương thức của nó:
Mã:
Sheets("Sheet1").Select
Sheets(1).Select

Các UserForm thì được ông "lãnh đạo" là đối tượng collection UserForms "điều hành". UserForms là class trong module VBA. Code như sau thì sẽ chạy
Mã:
[SIZE=4][COLOR=#ff0000]VBA.[/COLOR][/SIZE]UserForms.Add("UserForm1").Show

Tất cả mọi đối tượng - collection dùng để "điều hành" những đối tượng cùng chủng loại thì có lôgíc giống nhau: Sheets, Controls, UserForms hay các đối tượng khác.
Nên nhớ là khi ta viết tắt Sheets(1) là ta đang truy cập tới thuộc tính mặc định. Mà thưuộc tính mặc định ở đây là Item. Và khi cần truy cập tới Item cụ thể thì ta phải cung cấp Index là tên hoặc chỉ số trong tập của đối tượng.
 
Lần chỉnh sửa cuối:
Upvote 0
Tôi luôn có thắc mắc như sau, các đối tượng đều có hàm gọi theo chuỗi tên hoặc trực tiếp.

VD:

Sheet1.Select

Sheets("Sheet1").Select

TextBox1.SetFocus

Controls("TextBox1").SetFocus

Call Macro

Run "Macro"

v.v...

Thế nhưng với UserForm thì tôi chưa biết nó gọi dạng chuỗi như thế nào.

UserForm1.Show

Nhưng không có UserForms("UserForm1").Show

Có thể là tôi chưa biết, nhưng nếu ai biết thì hướng dẫn cho tôi hàm nào gọi UserForm bằng dạng chuỗi.

Cám ơn rất nhiều.

Mấy kiến thức này đã có từ đời nào trên diễn đàn rồi:
http://www.giaiphapexcel.com/forum/showthread.php?43681-Tạo-và-xóa-UserForm
http://www.giaiphapexcel.com/forum/showthread.php?26010-Nhờ-cả-nhà-giúp-đỡ-double-kick-load-form
và thậm chí là trong chính topic của Nghĩa cũng từng đề cập:
http://www.giaiphapexcel.com/forum/showthread.php?52838-Lỗi-Show-Form-khi-UserForm_QueryClose
vân vân
 
Lần chỉnh sửa cuối:
Upvote 0

Đúng là bài này em có hỏi và Thầy có làm và liên quan đến VBA.UserForms.Add("UserForm2").Show, nhưng thực sự vì bài đó em quan tâm đến thuật toán, chưa nghĩ đến việc này nên chưa nghiên cứu và ... không nhớ hihihihi.

Em lại thấy mỗi lần có .Add là tạo ra một cái gì đó mới nên cũng ngại đụng vào vì mình chưa biết đầu đuôi ngọn ngành như thế nào, hỏi trước cho chắc ăn.

Để em thử dùng VBA.UserForms.Add("UserForm2") vào bài viết của em xem có được không rồi sẽ tính tiếp vì không biết nó có tính chất như thay thế một form hay không.

Cám ơn các Thầy đã tận tình hướng dẫn.
 
Upvote 0
Thử "nghịch" với UserForm:

Nếu có UserForm1 có TextBox1 và lần lượt các nút lệnh:

Mã:
Private Sub CommandButton1_Click()
    VBA.UserForms.Add("UserForm1").Controls("TextBox1").Text = "VBA.UserForms"
End Sub

Private Sub CommandButton2_Click()
    UserForm1("TextBox1").Text = "UserForm1"
End Sub

Private Sub CommandButton3_Click()
    Me("TextBox1").Text = "ME"
End Sub


Riêng với CommandButton1 là không thực hiện gì, nhưng cũng chẳng báo lỗi.

Khi thoát form, nếu đã bấm CommandButton1 thì dường như nó còn một cái gì đó ẩn ở đâu đó mà mình bấm vào nút Reset trên Toolbar của VBA thì có hiện tượng như là dừng thực thi của một form đang load hay dừng thực thi một thủ tục. Ngược lại, không bấm nút đó thì nó không xảy ra như hiện tượng trên.

Như vậy, dùng VBA.UserForms.Add("UserForm1") vẫn chưa đáp ứng được nhu cầu cần thực hiện.

Nói rõ cái nhu cầu là dùng thủ tục để tác động lên 1 form bất kỳ đang hiện hành, dùng vòng lặp quét qua các TextBox* (với * chạy từ 1 đến 5) vì thế tên của Controls luôn ở dạng chuỗi.
 

File đính kèm

Upvote 0
Thử "nghịch" với UserForm:

Nếu có UserForm1 có TextBox1 và lần lượt các nút lệnh:

Mã:
Private Sub CommandButton1_Click()
    VBA.UserForms.Add("UserForm1").Controls("TextBox1").Text = "VBA.UserForms"
End Sub

Private Sub CommandButton2_Click()
    UserForm1("TextBox1").Text = "UserForm1"
End Sub

Private Sub CommandButton3_Click()
    Me("TextBox1").Text = "ME"
End Sub


Riêng với CommandButton1 là không thực hiện gì, nhưng cũng chẳng báo lỗi.

Khi thoát form, nếu đã bấm CommandButton1 thì dường như nó còn một cái gì đó ẩn ở đâu đó mà mình bấm vào nút Reset trên Toolbar của VBA thì có hiện tượng như là dừng thực thi của một form đang load hay dừng thực thi một thủ tục. Ngược lại, không bấm nút đó thì nó không xảy ra như hiện tượng trên.

Như vậy, dùng VBA.UserForms.Add("UserForm1") vẫn chưa đáp ứng được nhu cầu cần thực hiện.

Nói rõ cái nhu cầu là dùng thủ tục để tác động lên 1 form bất kỳ đang hiện hành, dùng vòng lặp quét qua các TextBox* (với * chạy từ 1 đến 5) vì thế tên của Controls luôn ở dạng chuỗi.

Trước tiên ta mổ xẻ code, chỗ đỏ đỏ

Mã:
Private Sub CommandButton1_Click()
    [COLOR=#ff0000]VBA.UserForms.Add("UserForm1")[/COLOR].Controls("TextBox1").Text = "VBA.UserForms"
End Sub

Nếu code của Sheet1 - CommandButton1_Click là

Mã:
Private Sub CommandButton1_Click()
    VBA.UserForms.Add("UserForm1").Show
End Sub

thì khi thực hiện code VBA.UserForms.Add("UserForm1").Show rõ ràng là UserForm1 chưa có trong collection. Vì sao? Vì UserForms là tập các UserForm đã được LOAD. Mà ở thời điểm thực hiện code trên thì chưa có UserForm nào được load. Vậy thì code trên thực hiện:
Thêm UserForm1 vào tập - danh sách đã được LOAD và LOAD UserForm1 (load thôi nhé chứ UserForm1 không được hiển thị)
Mã:
VBA.UserForms.Add("UserForm1")

Phương thức Add trả về object UserForm1 (pointer), và method Show của "vị" này được gọi
Viết tường minh thì là thế này:

Mã:
Dim obj as Object
    '   phương thức Add load UserForm1 và trả về pointer của UserForm1. Pointer này được ghi nhớ trong biến obj để dùng về sau
    set obj = VBA.UserForms.Add("UserForm1")
    '  dùng obj hiển thị UserForm1 đã được load
    obj.Show

Nhưng hiện nay thì code của Sheet1 - CommandButton1_Click là

Mã:
Private Sub CommandButton1_Click()
    UserForm1.Show
End Sub

Khi thực hiện code UserForm1.Show thì trước tiên UserForm1 được load, vậy UserForm1 được thêm vào danh sách đã được load (nên nhớ là UserForms là collection các UserForm đã được load), sau đó nó được hiển thị (Show)

Vậy khi thực hiện code trong CommandButton1_Click thì UserForm1 đã được load và hiển thị, và nó đã được thêm vào collection "từ đời nào" rồi. Do UserForm1 đã có trong UserForms "từ đời nào" rồi nên khi Nghĩa gọi Add: VBA.UserForms.Add("UserForm1") thì một phiên bản của UserForm1 được load và .Controls("TextBox1").Text = "VBA.UserForms" được thực hiện cho phiên bản này chứ không phải cho phiên bản chính. Mà phiên bản thứ 2 mới chỉ được load chứ chưa hiển thị nên cho dù Text trong TextBox1 đã thay đổi nhưng Nghĩa không nhìn thấy.

Nếu Nghĩa sửa code thành:

Mã:
Private Sub CommandButton1_Click()
    With VBA.UserForms.Add("UserForm1")
        .Controls("TextBox1").Text = "VBA.UserForms"
        '  Show để nhìn thấy
        .Show
    End With
End Sub

Thì Nghĩa sẽ thấy là đúng có phiên bản thứ 2 và TextBox1 của nó được thay đổi.
---------------
Vậy thì khi UserForm đã có trong UserForms thì không dùng ADD mà chỉ ĐỌC ra thôi.
Đọc bằng cách nào? Hóa ra là khi dùng Item thì:
1. Chỉ có thể dùng Index là chỉ số
2. Chỉ số - Index tính từ 0 chứ không phải tính từ 1.

Vậy thì Nghĩa phải viết:

Mã:
Private Sub CommandButton1_Click()
    VBA.UserForms.Item(0).Controls("TextBox1").Text = "VBA.UserForms"
    '   hoặc (do Item là mặc định) 
    VBA.UserForms(0).Controls("TextBox1").Text = "VBA.UserForms" 
End Sub

Nếu Nghĩa muốn dùng TÊN (vd. do có 2 UserForm đã được load mà Nghĩa cứ muốn thao tác với UserForm1) thì tôi nghĩ là phải dùng FOR thôi:

Mã:
Private Sub CommandButton1_Click()
Dim Obj As Object
        For Each Obj In VBA.UserForms
            If StrComp(Obj.Name, "UserForm1", vbTextCompare) = 0 Then
                Obj.Controls("TextBox1").Text = "VBA.UserForms"
                Exit For
            End If
        Next Obj
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Trước tiên ta mổ xẻ code, chỗ đỏ đỏ

Mã:
Private Sub CommandButton1_Click()
    [COLOR=#ff0000]VBA.UserForms.Add("UserForm1")[/COLOR].Controls("TextBox1").Text = "VBA.UserForms"
End Sub

Nếu code của Sheet1 - CommandButton1_Click là

Mã:
Private Sub CommandButton1_Click()
    VBA.UserForms.Add("UserForm1").Show
End Sub

thì khi thực hiện code VBA.UserForms.Add("UserForm1").Show rõ ràng là UserForm1 chưa có trong collection. Vì sao? Vì UserForms là tập các UserForm đã được LOAD. Mà ở thời điểm thực hiện code trên thì chưa có UserForm nào được load. Vậy thì code trên thực hiện:
Thêm UserForm1 vào tập - danh sách đã được LOAD và LOAD UserForm1 (load thôi nhé chứ UserForm1 không được hiển thị)
Mã:
VBA.UserForms.Add("UserForm1")

Phương thức Add trả về object UserForm1 (pointer), và method Show của "vị" này được gọi
Viết tường minh thì là thế này:

Mã:
Dim obj as Object
    '   phương thức Add load UserForm1 và trả về pointer của UserForm1. Pointer này được ghi nhớ trong biến obj để dùng về sau
    set obj = VBA.UserForms.Add("UserForm1")
    '  dùng obj hiển thị UserForm1 đã được load
    obj.Show

Nhưng hiện nay thì code của Sheet1 - CommandButton1_Click là

Mã:
Private Sub CommandButton1_Click()
    UserForm1.Show
End Sub

Khi thực hiện code UserForm1.Show thì trước tiên UserForm1 được load, vậy UserForm1 được thêm vào danh sách đã được load (nên nhớ là UserForms là collection các UserForm đã được load), sau đó nó được hiển thị (Show)

Vậy khi thực hiện code trong CommandButton1_Click thì UserForm1 đã được load và hiển thị, và nó đã được thêm vào collection "từ đời nào" rồi. Do UserForm1 đã có trong UserForms "từ đời nào" rồi nên khi Nghĩa gọi Add: VBA.UserForms.Add("UserForm1") thì một phiên bản của UserForm1 được load và .Controls("TextBox1").Text = "VBA.UserForms" được thực hiện cho phiên bản này chứ không phải cho phiên bản chính. Mà phiên bản thứ 2 mới chỉ được load chứ chưa hiển thị nên cho dù Text trong TextBox1 đã thay đổi nhưng Nghĩa không nhìn thấy.

Nếu Nghĩa sửa code thành:

Mã:
Private Sub CommandButton1_Click()
    With VBA.UserForms.Add("UserForm1")
        .Controls("TextBox1").Text = "VBA.UserForms"
        '  Show để nhìn thấy
        .Show
    End With
End Sub

Thì Nghĩa sẽ thấy là đúng có phiên bản thứ 2 và TextBox1 của nó được thay đổi.
---------------
Vậy thì khi UserForm đã có trong UserForms thì không dùng ADD mà chỉ ĐỌC ra thôi.
Đọc bằng cách nào? Hóa ra là khi dùng Item thì:
1. Chỉ có thể dùng Index là chỉ số
2. Chỉ số - Index tính từ 0 chứ không phải tính từ 1.

Vậy thì Nghĩa phải viết:

Mã:
Private Sub CommandButton1_Click()
    VBA.UserForms.Item(0).Controls("TextBox1").Text = "VBA.UserForms"
    '   hoặc (do Item là mặc định) 
    VBA.UserForms(0).Controls("TextBox1").Text = "VBA.UserForms" 
End Sub

Nếu Nghĩa muốn dùng TÊN (vd. do có 2 UserForm đã được load mà Nghĩa cứ muốn thao tác với UserForm1) thì tôi nghĩ là phải dùng FOR thôi:

Mã:
Private Sub CommandButton1_Click()
Dim Obj As Object
        For Each Obj In VBA.UserForms
            If StrComp(Obj.Name, "UserForm1", vbTextCompare) = 0 Then
                Obj.Controls("TextBox1").Text = "VBA.UserForms"
                Exit For
            End If
        Next Obj
End Sub

Cám ơn Thầy rất nhiều.

Em có thắc mắc là, có phải Form nào đang Active thì Form đó được gọi là Form có Item là 0 phải không? Hay form nào load trước gọi là 0 ạ?
 
Upvote 0
Cám ơn Thầy rất nhiều.

Em có thắc mắc là, có phải Form nào đang Active thì Form đó được gọi là Form có Item là 0 phải không? Hay form nào load trước gọi là 0 ạ?

Thế bạn không kiểm tra được à? THêm 2 UserForm --> Trên mỗi Form có 1 CommandButton --> Trong CommanButton_Click của mỗi Form code duyệt từ VBA.UserForms(0) tới VBA.UserForms(VBA.UserForms.Count-1) rồi đọc tên của chúng ra --> Click Form1 để active nó --> click Button trên Form --> xem anh nào có index = 0 --> Click Form2 để active nó --> click Button trên Form --> xem anh nào có index = 0
Nhưng cái FOR kia nó có phức tạp đâu mà bạn không muốn dùng?
 
Upvote 0
Thế bạn không kiểm tra được à? THêm 2 UserForm --> Trên mỗi Form có 1 CommandButton --> Trong CommanButton_Click của mỗi Form code duyệt từ VBA.UserForms(0) tới VBA.UserForms(VBA.UserForms.Count-1) rồi đọc tên của chúng ra --> Click Form1 để active nó --> click Button trên Form --> xem anh nào có index = 0 --> Click Form2 để active nó --> click Button trên Form --> xem anh nào có index = 0
Nhưng cái FOR kia nó có phức tạp đâu mà bạn không muốn dùng?

Vừa kiểm tra xong. Form nào load đầu tiên thì đó là 0 form load kế tiếp là 1 cho đến n.

Riêng với việc load form và kiểm tra, phải để ở chế độ ShowModal là False mới kiểm tra được.

Cám ơn Thầy.
 
Upvote 0

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

Back
Top Bottom