ListViewItems managen

  • Ich schreibe gerade an einem Programm, das eine ListView enthält. Dabei sind einige Fragen entstanden.

    1. Frage: In der Help-File steht unter Limits, dass man maximal 4093 Control-Elemente erstellen kann. Weil jedes ListViewItem ein eigenes Control-Element ist, kann man also maximal 4092 ListViewItems in einer ListView erstellen. Richtig? Und was würde passieren, wenn diese Grenze doch überschritten wird?

    2. Frage: In dem Programm, das ich gerade schreibe, enthält die ListView momentan 350 ListViewItems. Ich schätze, dass es irgendwann um die 1000 ListViewItems sein werden. Kann man voraussagen, ab wie viel ListViewItems die Performance problematisch werden könnte?

    Ich arbeite wie gesagt an einem Programm mit einer ListView. Ich möchte kurz die grundlegende Programmfunktion meiner ListView skizzieren:
    Die ListView zeigt eine Anzahl von ListViewItems. Über separate Combos und Inputs kann der Benutzer eine Auswahl treffen. Die ListView soll dann nur noch den passenden Teil der gesamten Anzahl an ListViewItems anzeigen. Diesbezüglich habe ich verschiedene Lösungsansätze entworfen, bin aber zu keinem zufriedenstellenden Schluss gekommen.
    1. Ansatz: Die unpassenden Items werden ausgeblendet. Ändert sich die Auswahl erneut, können die Items bei Bedarf wieder eingeblendet werden. Der Ansatz scheitert daran, dass sich ListViewItems nicht mittels $GUI_HIDE ausblenden lassen.
    2. Ansatz: Die unpassenden Items werden gelöscht. Ändert sich die Auswahl erneut, müssen die Items bei Bedarf neu erstellt werden. Problematisch ist, dass ich die erneut erstellten Items nicht an ihrer alten Position wieder erstellen konnte. Sie werden hinter das letzte Item angefügt, sowohl Layout-technisch als auch bezüglich der Nummern der controlIDs. Dadurch entstand ein solches Durcheinander, dass meine Programmroutinen sich nicht mehr wie geplant verhielten und unvorhergesehene Ergebnisse rauskamen. Außerdem entsteht durch das ständige Hinten-ranfügen ein weiteres Problem: die controlIDs könnten leicht über die Nummer 4093 kommen (während die tatsächliche Anzahl an ListViewItems bei (zur Zeit) maximal 350 bleiben würde). Ich bin mir nicht sicher, ob dann die Beschränkung durch das oben erwähnte Limit greifen würde.
    3. Ansatz: Es werden erst alle Items gelöscht, und dann nur die passenden Items neu erstellt. Bei jeder Änderung der Auswahl würden also zunächst alle Items gelöscht. Dieser Ansatz funktionierte im Gegensatz zu den anderen wenigstens, aber es entstehen schon bei 350 Items unnötig lange Wartezeiten bis das Ergebnis erscheint.

    Welche Vorschläge habt ihr? Oder sind noch Dinge unklar?

  • Hi!

    Ich weiß nicht ob ich dasjetzt richtig verstanden habe aber wenn du einfach nur etwas anderes in einem Listviewitem anzeigen lassen willst, dann kannst du doch einfach mittels GUICtrlSetData das Item mit dem entsprechenden Handle verändern.

    MFG eagle

  • Nachdem der Benutzer eine Auswahl getroffen hat, sollen von den 350 ListViewItems z.B. nur noch 50 angezeigt werden. Ich komme also nicht drum herum einige ListViewItems mit GUICtrlDelete zu löschen. Man könnte natürlich versuchen, die anzuzeigenden ListViewItems mittels GUICtrlSetData auf die ersten ListViewItems zu verschieben, so dass ich nur von hinten (und nicht zwischendrin) Items löschen muss.

    Zum Limit: mal dieses Programm ausführen...

    [autoit]

    #include <GUIConstants.au3>

    [/autoit][autoit][/autoit][autoit]

    Opt("GUIOnEventMode", 1)
    #Region ### START Koda GUI section ### Form=
    $Form1 = GUICreate("Form1", 633, 447, 193, 125)
    GUISetOnEvent($GUI_EVENT_CLOSE, "Form1Close")
    $ListView1 = GUICtrlCreateListView("Column", 8, 8, 617, 433)
    For $x = 1 To 5000
    GUICtrlCreateListViewItem( $x, $ListView1)
    Next
    GUISetState(@SW_SHOW)
    #EndRegion ### END Koda GUI section ###

    [/autoit][autoit][/autoit][autoit]

    While 1
    Sleep(100)
    WEnd

    [/autoit][autoit][/autoit][autoit]

    Func Form1Close()
    Exit
    EndFunc

    [/autoit]


    ... und siehe da, bei ListViewItem Nummer 4095 ist Schluss. So wesentlich höher ist das nicht ;) . Fehlermeldungen gibt's keine, nicht mal im Output (F8 ).

  • So stelle ich mir das Programm vor (vereinfachtes Testprogramm):

    [autoit]

    #include <GUIConstants.au3>
    Opt("GUIOnEventMode", 1)

    [/autoit][autoit][/autoit][autoit]

    Global $Array[15] = ["Adler", "Affe", "Bär", "Chamäleon", "Dachs", "Elefant", "Fisch", "Fuchs", "Gans", "Giraffe", "Hamster", "Hase", "Hund", "Igel", "Jaguar"]
    Global $ItemsArray[15]

    [/autoit][autoit][/autoit][autoit]

    $Form = GUICreate("Test", 633, 454, 193, 115)
    GUISetOnEvent($GUI_EVENT_CLOSE, "FormClose")
    GUICtrlCreateLabel("Anfangsbuchstabe:", 8, 8, 100, 21)
    $Input = GUICtrlCreateInput("Suchen", 120, 8, 169, 21)
    GUICtrlSetOnEvent(-1, "InputChange")
    $ListView = GUICtrlCreateListView("Tiere ", 8, 40, 617, 409)
    GUISetState(@SW_SHOW)

    [/autoit][autoit][/autoit][autoit]

    For $i = 0 To UBound($Array)-1
    $ItemsArray[$i] = GUICtrlCreateListViewItem($Array[$i], $ListView)
    GUICtrlSetOnEvent(-1, "ItemClick")
    Next

    [/autoit][autoit][/autoit][autoit]

    While 1
    Sleep(100)
    WEnd

    [/autoit][autoit][/autoit][autoit]

    Func FormClose()
    Exit
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    Func ItemClick()
    MsgBox(0, "controlID", "controlID = " & @GUI_CtrlId, 2)
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    Func InputChange()
    $SearchString = "(?i)\A" & GUICtrlRead($Input)
    For $i = 0 To UBound($Array)-1
    If StringRegExp($Array[$i], $SearchString) = 0 Then ;wenn Anfangsbuchstabe nicht übereinstimmt, dann Delete
    GUICtrlDelete($ItemsArray[$i])
    EndIf
    If StringRegExp($Array[$i], $SearchString) = 1 AND GUICtrlGetState($ItemsArray[$i]) = -1 Then ;wenn Anfangsbuchstabe übereinstimmt und Item nicht existiert, dann Create
    $ItemsArray[$i] = GUICtrlCreateListViewItem($Array[$i], $ListView)
    GUICtrlSetOnEvent(-1, "ItemClick")
    EndIf
    Next
    EndFunc

    [/autoit]


    Eigentlich simpel und selbsterklärend. Es funktioniert aber nicht wie es soll (bitte ausprobieren).
    Wie soll ich die InputChange() Funktion ändern? Ich bitte um Vorschläge.

    • Offizieller Beitrag

    Ich würde einen anderen Lösungsansatz vorschlagen:

    Im Gegensatz zu einzelnen Listview-Items kann man die gesamte Liestview durchaus verstecken/anzeigen, also...
    Generiere eine zweite Liestview (genau über der ersten), in der Du nur die Auswahl erstellst. Dann verstecke die erste Listview und lasse die zweite anzeigen. Bei einer erneuten Auswahl versteckst/oder löschst Du die zweite und läßt wieder die erste anzeigen. Auf diese Art verändern sich die Control-IDs aus der Ursprungs-Listview nicht.

  • Die Korrektur des oben angeführten Testprogramms ist einfach.
    Hier die veränderte InputChange() Funktion:

    [autoit]

    Func InputChange()
    $SearchString = "(?i)\A" & GUICtrlRead($Input)
    For $i = 0 To UBound($Array)-1
    If StringRegExp($Array[$i], $SearchString) = 0 Then ;wenn Anfangsbuchstabe nicht übereinstimmt, dann Delete
    GUICtrlDelete($ItemsArray[$i])
    $ItemsArray[$i] = 0 ; 1. Zeile hinzugefügt
    EndIf
    If StringRegExp($Array[$i], $SearchString) = 1 AND GUICtrlGetState($ItemsArray[$i]) = -1 Then ;wenn Anfangsbuchstabe übereinstimmt und Item nicht existiert, dann Create
    $ItemsArray[$i] = GUICtrlCreateListViewItem($Array[$i], $ListView)
    GUICtrlSetOnEvent(-1, "ItemClick")
    EndIf
    Next
    EndFunc

    [/autoit]


    Ich brauchte nur ein Zeile Code hinzufügen:
    $ItemsArray[$i] = 0
    Diese Zeile bewirkt, dass von einem gelöschten Item die controlID aus dem Array gelöscht wird. Dadurch wird verhindert, dass ein später zufällig mit der gleichen controlID erstelltes Item nicht versehentlich gelöscht wird.

    Es bleibt das Problem, dass neu erstellte Items hinter das letzte vorhandene Item platziert werden. Dadurch können die controlIDs unaufhörlich steigen. Man füge, um das zu testen, noch eine weitere Zeile Code und eine neue Funktion ein:

    [autoit]

    Func InputChange()
    $SearchString = "(?i)\A" & GUICtrlRead($Input)
    For $i = 0 To UBound($Array)-1
    If StringRegExp($Array[$i], $SearchString) = 0 Then ;wenn Anfangsbuchstabe nicht übereinstimmt, dann Delete
    GUICtrlDelete($ItemsArray[$i])
    $ItemsArray[$i] = 0 ; 1. Zeile hinzugefügt
    EndIf
    If StringRegExp($Array[$i], $SearchString) = 1 AND GUICtrlGetState($ItemsArray[$i]) = -1 Then ;wenn Anfangsbuchstabe übereinstimmt und Item nicht existiert, dann Create
    $ItemsArray[$i] = GUICtrlCreateListViewItem($Array[$i], $ListView)
    GUICtrlSetOnEvent(-1, "ItemClick")
    EndIf
    Next
    DisplayControlIDs() ; 2. Zeile hinzugefügt
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    Func DisplayControlIDs() ; neue Funktion hinzugefügt
    $ItemsArrayToString = ""
    For $i = 0 To UBound($ItemsArray)-1
    $ItemsArrayToString &= $ItemsArray[$i] & " "
    Next
    MsgBox(0, "", $ItemsArrayToString)
    EndFunc

    [/autoit]


    Und noch ein weiteres Problem gibt es. Das Array, welches im Testprogramm Tiere enthält, ist auch im letztendlich geplanten Programm alphabetisch sortiert. Werden aber Items hinten rangefügt, dann ist die ListView nicht mehr alphabetisch sortiert. Um das zu testen, lösche man aus dem obigen Programm in der Zeile $SearchString = "(?i)\A" & GUICtrlRead($Input) die Zeichen \A , was bewirkt, dass die Suche nicht mehr auf den Anfangsbuchstaben beschränkt ist.

    Aus diesen Gründen verwerfe ich diesen Ansatz jetzt komplett.
    Ich werde also Ansatz 3 weiterentwickeln: Es werden erst alle Items gelöscht, und dann nur die passenden Items neu erstellt.
    Jetzt überlege ich, wie ich die Items am schnellsten lösche. Die Idee von Oscar, eine neue, leere ListView zu erstellen wäre eine Möglichkeit.

  • Ich komme nochmal auf die Limits zurück.

    1. Fall: Es werden mehr ListViewItems erstellt als in AutoIt möglich.
    Resultat: Bei Item Nummer 4095 ist Schluss. Jedes nachfolgende Control-Element wird nicht mehr erstellt. (Beispiel siehe oben 4. Post)

    2. Fall: Die ControlIDs überschreiten das Limit, während die Anzahl unter dem Limit bleibt.
    Beispiel: Schritt a) Es werden 3000 Items erstellt.
    Schritt b) Es werden alle Items bis auf das letzte gelöscht.
    Schritt c) Es werden 3000 neue Items erstellt.
    Problem: AutoIt erstellt die Items hinter dem letzten vorhandenen.
    Resultat: Die ControlIDs überschreiten eine magische Grenze von 4098. AutoIt fängt beim Vergeben der ControlIDs wieder von vorne an. Wichtig: AutoIt vergibt automatisch nur die unvergebenen ControlIDs (das heißt die von bereits gelöschten Controls) erneut.

    Beispielskript: (auf Item Nummer 1096 Klicken!)

    [autoit]

    #include <GUIConstants.au3>

    [/autoit][autoit][/autoit][autoit]

    Opt("GUIOnEventMode", 1)
    Global $ItemsArray1[3000]
    Global $ItemsArray2[3000]

    [/autoit][autoit][/autoit][autoit]

    GUICreate("ListViewItems Löschen", 633, 447, 193, 125)
    GUISetOnEvent($GUI_EVENT_CLOSE, "Form1Close")
    $ListView = GUICtrlCreateListView("Number", 8, 40, 617, 401)
    For $i = 0 To UBound($ItemsArray1)-1
    $ItemsArray1[$i] = GUICtrlCreateListViewItem($i+1, $ListView)
    GUICtrlSetOnEvent(-1, "ItemClick")
    Next
    For $i = 0 To UBound($ItemsArray1)-2
    GUICtrlDelete($ItemsArray1[$i])
    Next
    For $i = 0 To UBound($ItemsArray2)-1
    $ItemsArray2[$i] = GUICtrlCreateListViewItem($i+1, $ListView)
    GUICtrlSetOnEvent(-1, "ItemClick")
    Next
    GUISetState(@SW_SHOW)

    [/autoit][autoit][/autoit][autoit]

    While 1
    Sleep(100)
    WEnd

    [/autoit][autoit][/autoit][autoit]

    Func Form1Close()
    Exit
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    Func ItemClick()
    MsgBox(0, "controlID", "controlID = " & @GUI_CtrlId, 2)
    EndFunc

    [/autoit]
    • Offizieller Beitrag

    Schau Dir mal die _GUICtrlListview-Funktionen in den UDFs an.

    Die habe ich bei meinem Programm (Rekursive Filelist) benutzt. Dort gibt es definitiv nicht diese Grenzen, denn ich habe gerade mal meine Festplatte C: gescannt und in dem Listview befanden sich dann über 119.000 Dateien.

    Hier mal ein Beispiel:

    Spoiler anzeigen
    [autoit]


    #include <GuiConstantsEx.au3>
    #include <GuiListView.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global $GUI = GUICreate('Test', 800, 600, -1, -1)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit]

    Global $GUI_Listview = GUICtrlCreateListView('|||', 10, 10, 780, 580)
    _GUICtrlListView_SetColumn(GUICtrlGetHandle($GUI_Listview), 0, 'Spalte 1', 250, 0)
    _GUICtrlListView_SetColumn(GUICtrlGetHandle($GUI_Listview), 1, 'Spalte 2', 250, 0)
    _GUICtrlListView_SetColumn(GUICtrlGetHandle($GUI_Listview), 2, 'Spalte 3', 279, 0)

    [/autoit] [autoit][/autoit] [autoit]

    Global $aListview[10000][3]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To 9999
    $aListview[$i][0] = $i
    $aListview[$i][1] = Random(1, 3000)
    $aListview[$i][2] = Random(1, 3000)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    _GUICtrlListView_SetItemCount(GUICtrlGetHandle($GUI_Listview), 9999)
    _GUICtrlListView_AddArray($GUI_Listview, $aListview)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, 'Test', 'Alle Einträge Löschen!')

    [/autoit] [autoit][/autoit] [autoit]

    _GUICtrlListView_DeleteAllItems(GUICtrlGetHandle($GUI_Listview))

    [/autoit] [autoit][/autoit] [autoit]

    ReDim $aListview[3000][3]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To 2999
    $aListview[$i][0] = $i
    $aListview[$i][1] = Random(1, 3000)
    $aListview[$i][2] = Random(1, 3000)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    _GUICtrlListView_SetItemCount(GUICtrlGetHandle($GUI_Listview), 2999)
    _GUICtrlListView_AddArray($GUI_Listview, $aListview)

    [/autoit] [autoit][/autoit] [autoit]

    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

    [/autoit]