简体   繁体   中英

VBA Excel ListView Checkboxes do not show in Userform

I have a UserForm with a MultipageControl (name Controller_MultiPage). At runtime my code adds pages to the Multipage and creates a newListView on each page. Every ListView has:

      With newListView
        .MultiSelect = False
        .Width = Controller_MultiPage.Width - 10
        .Height = Controller_MultiPage.Height - 20
        .View = lvwReport
        .HideColumnHeaders = False
        .ColumnHeaders.Add Text:="Signal Name", Width:=.Width / 10 * 4
        .ColumnHeaders.Add Text:="Type", Width:=.Width / 10
        .ColumnHeaders.Add Text:="I/O", Width:=.Width / 10
        .ColumnHeaders.Add Text:="Description", Width:=.Width / 10 * 4
        .CheckBoxes = True
        .FullRowSelect = True
    End With

then I populate the newListView with data from an XML file:

         For Each node In list
            With node.Attributes
                Set listItem = newListView.ListItems.Add(Text:=.getNamedItem("Name").Text)
                listItem.ListSubItems.Add = .getNamedItem("Type").Text
                listItem.ListSubItems.Add = IIf(.getNamedItem("Input").Text = "1", "IN", "OUT")
                listItem.ListSubItems.Add = .getNamedItem("Description").Text
                listItem.Checked = False
            End With
        Next

but the checkboxes do not show. I can see the space for them in front of the first column and by clicking that space the checkbox of that particular row then appears. What I also noticed is that if I change the property

listItem.Checked = True

the behavior described above does not change, and when I click the free space in front of the first column (checkboxes space) the chsckbox that then shows up is still unchecked. Any idea?

The problem seems to be in the behavior of the MultiPage control. What I noticed was that if I forced the checkboxes' status (checked or unchecked) from the code, using the MultiPage_Change event, then the checkboxes show up.

So what I did was to create a class that holds the status of all checkboxes of all listviews on a single page, instantiate the Class for each ListView and store everything into a Dictionary, using the newListView.Name as Key

Then when the user changes page, the MultiPage_Change event that fires resets all the values of the checkboxes according to the Dictionary stored values.

In the Listview_N_ItemChecked event some other code updates the status of the item stored in the Dictionary. Kind of cumbersome but it works.

the class ( updated ):

' Class Name = ComponentsSignalsRecord
Option Explicit

Dim Name As String
' NOTE: Signals(0) will always be empty and status(0) will always be False
Dim Signals() As String
Dim Status() As Boolean
Dim Component As String

Property Let SetComponentName(argName As String)
    Component = argName
End Property

Property Get GetComponentName() As String
    GetComponentName = Component
End Property

Property Get getSignalName(argIndex) As String
    If argIndex >= LBound(Signals) And argIndex <= UBound(Signals) Then
        getSignalName = Signals(argIndex)
    Else
        getSignalName = vbNullString
    End If
End Property

Property Get dumpAll() As String()
    dumpAll = Signals
End Property

Property Get Count() As Long
    Count = UBound(Signals)
End Property

Property Get getStatus(argName As String) As Integer
    ' returns: -1 = Not Found; 1 = True; 0 = False
    getStatus = -1
    Dim i As Integer
    For i = 0 To UBound(Signals)
        If argName = Signals(i) Then getStatus = IIf(Status(i) = True, 1, 0): Exit For
    Next
End Property

Property Let setName(argName As String)
    Name = argName
End Property

Property Get getName() As String
    getName = Name
End Property

Public Sub UncheckAll()
    Dim i As Integer
    For i = 0 To UBound(Status)
        Status(i) = False
    Next
End Sub

Public Sub CheckAll()
    Dim i As Integer
    For i = 0 To UBound(Status)
        Status(i) = True
    Next
End Sub

Public Sub deleteSignal(argName As String)
    Dim spoolSignals() As String
    Dim spoolStatus() As Boolean
    Dim i As Integer
    spoolSignals = Signals
    spoolStatus = Status
    ReDim Signals(0)
    ReDim Status(0)
    For i = 1 To UBound(spoolSignals)
        If argName <> spoolSignals(i) Then
            ReDim Preserve Signals(UBound(Signals) + 1):    Signals(UBound(Signals)) = spoolSignals(i)
            ReDim Preserve Status(UBound(Status) + 1):      Status(UBound(Status)) = spoolStatus(i)
        End If
    Next
End Sub

Public Sub addSignal(argName As String, argValue As Boolean)
    Dim i As Integer
    For i = 0 To UBound(Signals)
        If argName = Signals(i) Then GoTo bye
    Next
    ReDim Preserve Signals(UBound(Signals) + 1)
    ReDim Preserve Status(UBound(Status) + 1)
    Signals(UBound(Signals)) = argName
    Status(UBound(Status)) = argValue
bye:
End Sub

Public Sub setStatus(argName As String, argValue As Boolean)
    Dim i As Integer
    For i = 0 To UBound(Signals)
        If argName = Signals(i) Then Status(i) = argValue: Exit For
    Next
End Sub

Private Sub Class_Initialize()
    ReDim Signals(0)
    ReDim Status(0)
End Sub

The Form relevant code. Module level:

Dim myDict As New Dictionary                           ' the Dictionary
Dim ComponentsSignalsList As ComponentsSignalsRecord   ' the Class

for each ListView created, may be one or more for every single MultiPage page:

Set ComponentsSignalsList = New ComponentsSignalsRecord
ComponentsSignalsList.setName = newListView.name

while populating the listview(s) in a loop for each single item added:

ComponentsSignalsList.addSignal List_Item.Text, List_Item.Checked

end of each loop, add the Class instance to the Dictionary:

myDict.Add ComponentsSignalsList.getName, ComponentsSignalsList

Now when changing Page in the MultiPage widget:

Private Sub Controller_MultiPage_Change()
    If isLoading Then Exit Sub   'avoid errors and undue behavior while initializing the MultiPage widget
    Dim locControl As Control
    Dim controlType As String: controlType = "ListView"
    With Controller_MultiPage
        For Each locControl In .Pages(.value).Controls
            If InStr(1, TypeName(locControl), controlType) > 0 Then
                Call Check_CheckBoxes(locControl)
            End If
        Next
    End With
End Sub

Private Sub Check_CheckBoxes(argListView As listView)
    If argListView.CheckBoxes = False Then Exit Sub   'some ListViews don't have checkboxes
    Dim myItem As ListItem
    For Each myItem In argListView.ListItems
        With myItem
            .Checked = myDict.Item(argListView.name).getStatus(.Text)
        End With
    Next
End Sub

when ticking/unticking a checkbox (note the the ItemChecked event handler is defined in another Class Public WithEvents , where the handler calls this method passing both the ListView ID and the Item object):

Public Sub ListViewsEvents_ItemCheck(argListView As listView, argItem As MSComctlLib.ListItem)
    With argItem
        myDict.Item((argListView .name).setStatus argName:=.Text, argValue:=.Checked
    End With
End Sub

I just found the answer to the same problem that I also had and I feel so stupid. I had the first column of the Listview set to Width = 0... and thus the checkboxes would no longer show. I gave it a width and everithing is back to normal...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM