Lập trình thì tới bước cuối cùng mới sử dụng tới tip & trick. Cái gì cũng có cách giải quyết theo phương pháp chuẩn của nó.
Như tôi nói ở trên, bài toán của bạn chắc chắn là bài toán chuẩn rồi [vì kiểu gì đám Microsoft coders trước kia cũng phải đụng tới].
Và nó được giải quyết thế này:
99% các câu hỏi liên quan tới lập trình [xử lý kỹ thuật] đều có thể giải quyết theo bước:
- Dịch câu hỏi sang tiếng anh
- Sử dụng Google
Ngoài "câu chuyện" của bạn là: gather/get/remove multi-selected VB listbox items như đã nói ở trên, bạn còn phải quan tâm tới limits của standard listbox [vì thế ít khi PM xịn sử dụng listbox cho dữ liệu nhiều]. Nếu ko bạn sẽ phải xử lý chuyện > 64k dữ liệu được load lên standard listbox thế nào? Làm thế nào để trong nháy mắt có thể load bụp 1 cái 1tr bản ghi lên listbox , v.v...
Ngoài ra, bạn còn phải nghĩ tới việc duplicate copy from a listbox to another, remove selected listbox items, etc... [all w/ fastest way]. Tất cả những việc như vậy đang đợi bạn và tôi chỉ có thể gợi ý đến đây mà thôi.
Như tôi nói ở trên, bài toán của bạn chắc chắn là bài toán chuẩn rồi [vì kiểu gì đám Microsoft coders trước kia cũng phải đụng tới].
Và nó được giải quyết thế này:
Conventional Visual Basic wisdom states that in order to gather
the selected items from a multi-select ListBox, you should loop
through all the items and test the Selected property. As with
all loops, however, this can potentially bog down slower CPU's.
As a much faster and more elegant alternative, you can use the
SendMessage[] API function instead.
As you probably know, this function lets you send a message to
one or more windows. The declaration statement conforms to the
following syntax:Mã:Sao chép.Private Declare Function SendMessage Lib "user32" _ Alias "SendMessageA" [ByVal hwnd As Long, ByVal wMsg _ As Long, ByVal wParam As Long, lParam As Any] As Long
Since we want to gather the listbox's selected items, we'll
send the LB_GETSELITEMS constant in the wMsg argument, which
you declare like so:Mã:Sao chép.Private Const LB_GETSELITEMS = &H191
In essence, the LB_GETSELITEMS message fills an array with the
index numbers of all the selected items. As a result, you must
pass two additional arguments with SendMessage[]. The first
argument should contain the maximum number of selected items.
To retrieve this value, you can simply use the listbox's
SelCount property. The second argument should hold the array
variable you want to fill with index values. The following
example shows how you might use this function:Mã:Sao chép.Dim ItemIndexes[] As Long, x As Integer, iNumItems As Integer iNumItems = ThisBox.SelCount If iNumItems Then ReDim ItemIndexes[iNumItems - 1] SendMessage ListBox1.hwnd, LB_GETSELITEMS, iNumItems, _ ItemIndexes[0] End If For x = 0 To iNumItems - 1 MsgBox ListBox1.List[ItemIndexes[x]] Next x
After being passed to the SendMessage function, iNumItems holds
the total number of selected items, and the ItemIndexes array
holds the selected item index values. Notice, that you must
pass a pointer to the ItemIndexes array, and not the array
itself. Thus, we passed ItemIndexes[0] into the SendMessage
function, not ItemIndexes[].Nhấp chuột vào đây để mở rộng...
99% các câu hỏi liên quan tới lập trình [xử lý kỹ thuật] đều có thể giải quyết theo bước:
- Dịch câu hỏi sang tiếng anh
- Sử dụng Google
Ngoài "câu chuyện" của bạn là: gather/get/remove multi-selected VB listbox items như đã nói ở trên, bạn còn phải quan tâm tới limits của standard listbox [vì thế ít khi PM xịn sử dụng listbox cho dữ liệu nhiều]. Nếu ko bạn sẽ phải xử lý chuyện > 64k dữ liệu được load lên standard listbox thế nào? Làm thế nào để trong nháy mắt có thể load bụp 1 cái 1tr bản ghi lên listbox , v.v...
Ngoài ra, bạn còn phải nghĩ tới việc duplicate copy from a listbox to another, remove selected listbox items, etc... [all w/ fastest way]. Tất cả những việc như vậy đang đợi bạn và tôi chỉ có thể gợi ý đến đây mà thôi.
hoa35ktxd đã viết:Trời, tôi muốn tìm được cách làm đơn giản trực tiếp trong VBA thôi, không đao to búa lớn làm gì vì cồng kềnh phức tạp.
Còn muốn xử lý như bạn nói thì tôi dùng .Net cho đơn giản hơn nhiều, chả phải DLL gì cho phức tạp và tốn công.
Thân.Nhấp chuột vào đây để mở rộng...
Vấn đề là con người khi chọn đường đi thì cần phải biết đường đó có đi được hay ko. Khi đã chứng minh là có tảng đá to lù lù trước mặt rồi thì nếu mà còn cố đi tiếp người ta gọi là....
Chúng ta phải biết cách học giải quyết vấn đề [nếu tôi làm VBA thật thì chắc chỉ khoảng 10 phút là tôi biết cái đó có làm được trên VBA hay ko. VBA hay Excel là đồ của M$ làm ra, chỉ cần vào msdn seach 1 lúc ko thấy đáp ứng thì về mặt chính thức là ko có cách nào xử lý. Mà khi M$ tạo ra nó nói đó là limited thì tốt nhất đừng tiếp tục làm tiếp vấn đề đó vì cái đó gọi là "biết là ko được mà cứ làm"].
Trong quá trình giải quyết vấn đề, khi làm ko được thì tìm, tìm không được thì làm cách khác chứ ko đi tiếp vào ngõ cụt [google & microsoft mà "solution not found" thì tớ cam đoan là chả có ai trên GPE làm được].
Bạn khi làm việc với Excel, với VBA thì phải hiểu là cái đó rất hạn chế. Properties và Methods của controls hạn chế như vậy thì tìm tiếp làm gì. Thế nên người ta mới gọi là VBA. Excel chỉ mạnh ở cái bảng tính với dữ liệu nhỏ thôi [mở files 500,000 dòng thì biết ngay] và VBA chỉ là 1 phần nhỏ script language bổ sung vào mà thôi.
hoa35ktxd đã viết:Ở đây thật đáng tiếc vì VBA không có cái thuộc tính SelCountNhấp chuột vào đây để mở rộng...
Để lấy được SelCount của ListBox thì bạn làm như sau:
Đoạn này để ở module nào đó
Mã:Sao chép.
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" [ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any] As Long
Public Const LB_GETSELCOUNT = &H190
Mã:Sao chép.
Public Function fnListBox_SelCount[objListBox as ListBox] as Long
' get the number of selected items in the listbox
fnListBox_SelCount = SendMessage[objListBox.hWnd, LB_GETSELCOUNT, 0&, ByVal 0&]
End Function
Sử dụng:
...
'// iNumItems = Listbox1.SelCount
iNumItems = fnListBox_SelCount[Listbox1]
....
Tớ vừa tra trên API Declaration và thấy hằng số LB_GETSELCOUNT nên đoán là nó sẽ chạy vì nếu viết như thế này thì nó sẽ lấy được tương tự như thuộc tính .ListCount
Mã:Sao chép.
' get the number of items in the source list
numItems = SendMessage[objListBox.hWnd, LB_GETCOUNT, 0&, ByVal 0&]
Nếu đoạn code trên mà thực hiện được thì coi như bài toán đã được giải quyết.
[Trước đó phải lấy được handle của objListBox đã vì trong VBA thì Listbox là Windowless control. Sử dụng hàm FindWindowEx]
Về mặt lý thuyết mà nói thì mọi ActiveX Controls trong HĐH Windows đều là các Window và đều có thể mở rộng nó thành những Window chuẩn. VBA chẳng qua cũng dùng tương tự như VB6 nhưng nó làm hẹp phạm vi đi mà thôi. Còn những gì trước kia phải làm bằng APIs với VS6 thì nay ông M$ biến nó thành bộ .NET Framework với hàng trăm nghìn tính năng được tích hợp theo từng object và trở thành thư viện để cho chúng ta dùng 1 cách dễ dàng.
Cảm ơn bạn hai2hai đã dành nhiều thời gian cho vấn đề của tôi.
Sau 1 hồi mày mò, tôi cũng đã làm được cái tôi cần, không dùng API, chỉ thông qua các thao tác khi sử dụng để lưu lại những Item được chọn. Có vẻ hơi dài dòng nhưng tôi thấy hiệu quả.
Các bạn tham khảo và cho ý kiến nhé, có thể vẫn còn sót tình huống.
Sau 1 hồi mày mò, tôi cũng đã làm được cái tôi cần, không dùng API, chỉ thông qua các thao tác khi sử dụng để lưu lại những Item được chọn. Có vẻ hơi dài dòng nhưng tôi thấy hiệu quả.
Các bạn tham khảo và cho ý kiến nhé, có thể vẫn còn sót tình huống.
PHP:Sao chép.
Dim Str As String 'Chuỗi lấy danh sách các Item được chọn
Dim Mdn As Boolean 'MouseDown
Dim Mm As Boolean ' MouseMove
Dim ShiftK As Integer
Dim StID As Long 'Vị trí ban đầu
Dim BotID As Long, TopID As Long 'Vị trí đầu, cuối danh sách
Private Sub L1_Change[]
If ShiftK = 0 Then 'Không có phím điều khiển nào được nhấn
If Not Mm Then 'Không kéo rê chuột
StID = L1.ListIndex
Str = "[" & L1.ListIndex & "]"
Else 'Kéo rê chuột
LienTuc
End If
ElseIf ShiftK = 1 Or ShiftK = 3 Then 'Bâìm Shift hoãòc Ctrl+Shift
LienTuc
ElseIf ShiftK = 2 Then 'Bấm Ctrl
If Not Mm Then 'Không kéo rê chuột
If L1.Selected[L1.ListIndex] Then StID = L1.ListIndex
If InStr[Str, "[" & L1.ListIndex & "]"] 0 Then
Str = Replace[Str, "[" & L1.ListIndex & "]", ""]
Else
If Mdn Then 'Chọn bằng chuột
Str = Str & "[" & L1.ListIndex & "]"
Else 'Chọn bằng phím
Str = "[" & L1.ListIndex & "]"
End If
End If
Else 'Kéo rê chuột
LienTuc
End If
End If
Me.Caption = Str
End Sub
Private Sub LienTuc[]
Str = ""
If StID > L1.ListIndex Then
BotID = StID: TopID = L1.ListIndex
Else
BotID = L1.ListIndex: TopID = StID
End If
For I = TopID To BotID
Str = Str & "[" & I & "]"
Next
End Sub
Private Sub L1_KeyDown[ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer]
ShiftK = Shift
End Sub
Private Sub L1_KeyUp[ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer]
ShiftK = Shift
End Sub
Private Sub L1_MouseDown[ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single]
ShiftK = Shift
Mdn = True
End Sub
Private Sub L1_MouseMove[ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single]
ShiftK = Shift
If Mdn Then Mm = True
End Sub
Private Sub L1_MouseUp[ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single]
ShiftK = Shift
Mm = False: Mdn = False
End Sub
Private Sub UserForm_Activate[]
Dim Lst[] As Long
For I = 1 To 1000
L1.AddItem I
Next
End Sub