Jump to content

Is GUICtrlDelete() recommended for zapping listview items?


CYCho
 Share

Recommended Posts

The following code compares time taken to zap listview items by two different methods. As deleting the populated control and creating a new one is much faster than _GUICtrlListView_DeleteAllItems(), is delete/recreate the recommended method for zapping listview items? I would like to know if there is any negative consequence of using this method.
 

#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <MsgBoxConstants.au3>

Example()

Func Example()
    Local $hGUI, $iI, $iTimer, $idListview
    Local $aItems[10000][4]
    For $iI = 0 To UBound($aItems) - 1
        $aItems[$iI][0] = "Item " & $iI
        $aItems[$iI][1] = "Item " & $iI & "-1"
        $aItems[$iI][2] = "Item " & $iI & "-2"
        $aItems[$iI][3] = "Item " & $iI & "-3"
    Next

    $hGUI = GUICreate("ListView Add Array", 400, 300)
    $idListview = GUICtrlCreateListView("", 2, 2, 394, 268)
    GUISetState(@SW_SHOW)

    _GUICtrlListView_AddColumn($idListview, "Items", 100)
    _GUICtrlListView_AddColumn($idListview, "SubItems 1", 100)
    _GUICtrlListView_AddColumn($idListview, "SubItems 2", 100)
    _GUICtrlListView_AddColumn($idListview, "SubItems 3", 100)

    _GUICtrlListView_SetItemCount($idListview, UBound($aItems))
    _GUICtrlListView_AddArray($idListview, $aItems)

    MsgBox($MB_SYSTEMMODAL, "Information", "Click OK to zap the listview control")
    $iTimer = TimerInit()
    _GUICtrlListView_DeleteAllItems($idListview)
    MsgBox($MB_SYSTEMMODAL, "Information", "Zapped by _GUICtrlListView_DeleteAllItems(): " & TimerDiff($iTimer) / 1000 & " seconds")

    _GUICtrlListView_SetItemCount($idListview, UBound($aItems))
    _GUICtrlListView_AddArray($idListview, $aItems)

    MsgBox($MB_SYSTEMMODAL, "Information", "Click OK to zap the listview control")
    $iTimer = TimerInit()
    GUICtrlDelete($idListview)
    $idListview = GUICtrlCreateListView("", 2, 2, 394, 268)

    _GUICtrlListView_AddColumn($idListview, "Items", 100)
    _GUICtrlListView_AddColumn($idListview, "SubItems 1", 100)
    _GUICtrlListView_AddColumn($idListview, "SubItems 2", 100)
    _GUICtrlListView_AddColumn($idListview, "SubItems 3", 100)
    MsgBox($MB_SYSTEMMODAL, "Information", "Zapped by deleting the control and creating a new one: " & TimerDiff($iTimer) / 1000 & " seconds")

    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete()
EndFunc   ;==>Example

 

Edited by CYCho
Link to comment
Share on other sites

@pixelsearch, In your post mentioned above you said: A worse idea would be to call _SendMessage($g_hListView, $LVM_DELETEALLITEMS) when all your items are natively created with GUICtrlCreateListViewItem(). In this case, deleting the listview control and creating a new one may be a viable option.

I used _SendMessage($g_hListView, $LVM_DELETEALLITEMS) in my latest update(3.0.1.6) of zPlayer and I like it. You are full of ideas and, as your avatar shows, you are very good at scholarly details. I respect you and I am glad to have you nearby.

Edited by CYCho
Link to comment
Share on other sites

As explained by @pixelsearch using _SendMessage($g_hListView, $LVM_DELETEALLITEMS) on natively created LVI will create holes (leaks) of control ids.  Normally, when you delete a control (with GUICtrlDelete), its ID is recuperated by the GUI system.  When you use the send message, it is not.  After a while you may end up lacking ID, and your application will fail.  Here an example that show the purpose :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>
#include <SendMessage.au3>
#include <ListViewConstants.au3>

Example()

Func Example()
  Local $hGUI = GUICreate("listview items", 220, 250, 100, 200, -1)
  Local $idListview = GUICtrlCreateListView("col1  |col2|col3  ", 10, 10, 200, 150)
  Local $idButton1 = GUICtrlCreateButton("Send", 75, 170, 70, 20)
  Local $idButton2 = GUICtrlCreateButton("Delete", 75, 195, 70, 20)
  Local $idItem1 = GUICtrlCreateListViewItem("item2|col22|col23", $idListview)
  Local $idItem2 = GUICtrlCreateListViewItem("item1|col12|col13", $idListview)
  Local $idItem3 = GUICtrlCreateListViewItem("item3|col32|col33", $idListview)
  GUISetState(@SW_SHOW)
  ConsoleWrite($idItem1 & "/" & $idItem2 & "/" & $idItem3 & @CRLF)

  While 1
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop
      Case $idButton1
        _SendMessage(GUICtrlGetHandle($idListview), $LVM_DELETEALLITEMS)
        Sleep(5000)
        $idItem1 = GUICtrlCreateListViewItem("item2|col22|col23", $idListview)
        $idItem2 = GUICtrlCreateListViewItem("item1|col12|col13", $idListview)
        $idItem3 = GUICtrlCreateListViewItem("item3|col32|col33", $idListview)
        ConsoleWrite($idItem1 & "/" & $idItem2 & "/" & $idItem3 & @CRLF)
      Case $idButton2
        For $i = $idItem1 to $idItem3
          GUICtrlDelete($i)
        Next
        Sleep(5000)
        $idItem1 = GUICtrlCreateListViewItem("item2|col22|col23", $idListview)
        $idItem2 = GUICtrlCreateListViewItem("item1|col12|col13", $idListview)
        $idItem3 = GUICtrlCreateListViewItem("item3|col32|col33", $idListview)
        ConsoleWrite($idItem1 & "/" & $idItem2 & "/" & $idItem3 & @CRLF)
    EndSwitch
  WEnd
EndFunc   ;==>Example

 

Edited by Nine
Link to comment
Share on other sites

Thanks.  So bottom line, your decision should be delete the whole ListView or delete individual ListView Items.  That's is only a question of speed and  programming elegancy.

Link to comment
Share on other sites

@Nine, I'm trying to mimic your test for sending a $LVM_DELETEALLITEMS message on listview items created by _GUICtrlListView_AddArray(), but I can't figure out how to do it.
@pixelsearch, You must have done that test, too.
Could any of you two gentlemen show me how to do it?

Link to comment
Share on other sites

Good morning @CYCho

Here the ways to delete all rows using non-native LVI.  I used the same array to fill the ListView.  In your case, you will need to recreate the array after each deletion.  Note that SendMessage is used within _GUICtrlListView_DeleteAllItems, so the first and last buttons perform exactly the same thing :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>
#include <SendMessage.au3>
#include <GuiListView.au3>

Example()

Func Example()
  Local $hGUI = GUICreate("listview items", 220, 250, 100, 200, -1)
  Local $idListview = GUICtrlCreateListView("col1|col2|col3", 10, 10, 200, 150)
  _GUICtrlListView_SetColumnWidth ($idListview, 0, 60)
  _GUICtrlListView_SetColumnWidth ($idListview, 1, 60)
  _GUICtrlListView_SetColumnWidth ($idListview, 2, 60)
  Local $idButton1 = GUICtrlCreateButton("Send", 75, 170, 70, 20)
  Local $idButton2 = GUICtrlCreateButton("Delete", 75, 195, 70, 20)
  Local $idButton3 = GUICtrlCreateButton("All", 75, 220, 70, 20)
  GUISetState(@SW_SHOW)

  ;create array of items
  Local $aItems[5][3]
  For $iI = 0 To UBound($aItems) - 1
    $aItems[$iI][0] = "Item " & $iI
    $aItems[$iI][1] = "Item " & $iI & "-1"
    $aItems[$iI][2] = "Item " & $iI & "-2"
  Next
  _GUICtrlListView_AddArray($idListview, $aItems)

  While 1
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop
      Case $idButton1
        _SendMessage(GUICtrlGetHandle($idListview), $LVM_DELETEALLITEMS)
        Sleep(5000)
        _GUICtrlListView_AddArray($idListview, $aItems)
      Case $idButton2
        For $i = 0 To _GUICtrlListView_GetItemCount($idListview) - 1
          _GUICtrlListView_DeleteItem($idListview, 0) ; always delete item 0 since number of items is reduced at each delete
        Next
        Sleep(5000)
        _GUICtrlListView_AddArray($idListview, $aItems)
      Case $idButton3
        _GUICtrlListView_DeleteAllItems($idListview) ; same thing as send message as this is perform within the UDF
        Sleep(5000)
        _GUICtrlListView_AddArray($idListview, $aItems)
     EndSwitch
  WEnd
EndFunc   ;==>Example

 

Link to comment
Share on other sites

@Nine, Good morning to you. To me it's 9:40 p.m. here in Seoul, Korea.
Let me elaborate what I don't understand. GUICtrlCreateListViewItem returns a control ID which is different from listview index. What control ID's are created when listview items are populated by _GUICtrlListView_AddArray? In your first example above, you demomstrated that sending a message to nativley created listview items will not delete the control ID's. My question is how do we check if control ID's created by _GUICtrlListView_AddArray are really deleted by sending a message.

Edited by CYCho
Link to comment
Share on other sites

I see.  Non-native controls do not generate control ID like native ones.  They are totally different.  Native controls are managed internally within the AutoIt GUI system.  While non-native are managed by Windows.  This is why you can use ListView UDF (for example) on controls coming from other application (not only AutoIt) while you can't with native functions.  So you don't have to worry about those leaks and holes when using non-native.  Hope this clarifies.

Link to comment
Share on other sites

Hi to both of you :)

@CYCho: I guess your initial message changed ;)
I remember it asked for an example showing what would happen if _GUICtrlListView_AddArray was used just before $LVM_DELETEALLITEMS

Here is one example, using _GUICtrlListView_AddArray, it shows that the recreated label control keeps its value of 5 (no leaks)

#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <MsgBoxConstants.au3>
#include <SendMessage.au3>
#include <WindowsConstants.au3>

Global $idListview, $idLabel, $iInc = 1
Global $aArray[3][3] = _
   [["item1", "c_12", "c_13"], _
    ["item2", "c_22", "c_23"], _
    ["item3", "c_32", "c_33"]]

Example()

Func Example()
    GUICreate("listview items", 220, 200)
    $idListview = GUICtrlCreateListView("col1  |col2|col3  ", 10, 10, 200, 150)
    Local $idButton = GUICtrlCreateButton("Click me", 25, 170, 70, 20)
    Create_Both()

    GUISetState(@SW_SHOW)

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop

            Case $idButton
                MsgBox($MB_SYSTEMMODAL, "id Label #" & $iInc & " = " & $idLabel, _
                    "Now deleting label & LV content, then recreating both")
                _SendMessage(GUICtrlGetHandle($idListview), $LVM_DELETEALLITEMS)
                Sleep(1000) ; just to show LV is empty on screen
                GUICtrlDelete($idLabel)
                $iInc += 1
                Create_Both()
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func Create_Both()
    _GUICtrlListView_AddArray($idListview, $aArray)
    $idLabel = GUICtrlCreateLabel("Label #" & $iInc, 130, 173, 70, 20)
EndFunc   ;==>Create_Both

And as already discussed and found in Nine's example yesterday, this is the "bad" way with plenty of leaks :

#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <MsgBoxConstants.au3>
#include <SendMessage.au3>
#include <WindowsConstants.au3>

Global $idListview, $idItem1, $idItem2, $idItem3, $idLabel, $iInc = 1

Example()

Func Example()
    GUICreate("listview items", 220, 200)
    $idListview = GUICtrlCreateListView("col1  |col2|col3  ", 10, 10, 200, 150)
    Local $idButton = GUICtrlCreateButton("Click me", 25, 170, 70, 20)
    Create_Both()

    GUISetState(@SW_SHOW)

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop

            Case $idButton
                MsgBox($MB_SYSTEMMODAL, "id Label #" & $iInc & " = " & $idLabel, _
                    "Now deleting label & LV content, then recreating both")
                _SendMessage(GUICtrlGetHandle($idListview), $LVM_DELETEALLITEMS)
                Sleep(1000) ; just to show LV is empty on screen
                GUICtrlDelete($idLabel)
                $iInc += 1
                Create_Both()
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func Create_Both()
    $idItem1 = GUICtrlCreateListViewItem("item1|col_12|col_13", $idListview)
    $idItem2 = GUICtrlCreateListViewItem("item2|col_22|col_23", $idListview)
    $idItem3 = GUICtrlCreateListViewItem("item3|col_32|col_33", $idListview)

    $idLabel = GUICtrlCreateLabel("Label #" & $iInc, 130, 173, 70, 20)
EndFunc   ;==>Create_Both

Have a great day/night !

Link to comment
Share on other sites

Just to add precision of what I have previously said

On 3/28/2021 at 8:00 AM, Nine said:

Note that SendMessage is used within _GUICtrlListView_DeleteAllItems, so the first and last buttons perform exactly the same thing

IF the control was created with native function (GUICtrlCreateListView), _GUICtrlListView_DeleteAllItems will try to delete the items one by one using native delete item as a first step.  If not successful (because items were created by non-native functions),  it will delete items using SendMessage.  So for long ListView, it may take quite awhile to perform the deletion.

In that particular situation where the list view is created natively and items non-natively, there would be a huge difference in speed.  So first and last buttons (in my snippet) will not perform the exact same thing.  I believe @CYCho, you already know that but I wanted to make that clarification for anyone reading this thread. 

Link to comment
Share on other sites

While trying this and that, I learned that SendMessage would not delete items if the listview were created by _GUICtrlListView_Create. I also found that item height is different depending on what function is used to create the listview. Item height in a listview created with GUICtrlCreateListView is 19, while it is 16 in a listview created with _GUICtrlListView_Create.

Edited by CYCho
Link to comment
Share on other sites

20 minutes ago, CYCho said:

I learned that SendMessage would not delete items if the listview were created by _GUICtrlListView_Create

You may have had a glitch in you code because it is working for me :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>
#include <SendMessage.au3>
#include <GuiListView.au3>

Example()

Func Example()
  Local $hGUI = GUICreate("listview items", 220, 250, 100, 200, -1)
  Local $idListview = _GUICtrlListView_Create($hGUI, "col1|col2|col3", 10, 10, 200, 150)
  _GUICtrlListView_SetColumnWidth ($idListview, 0, 60)
  _GUICtrlListView_SetColumnWidth ($idListview, 1, 60)
  _GUICtrlListView_SetColumnWidth ($idListview, 2, 60)
  Local $idButton1 = GUICtrlCreateButton("Send", 75, 170, 70, 20)
  GUISetState(@SW_SHOW)

  ;create array of items
  Local $aItems[5][3]
  For $iI = 0 To UBound($aItems) - 1
    $aItems[$iI][0] = "Item " & $iI
    $aItems[$iI][1] = "Item " & $iI & "-1"
    $aItems[$iI][2] = "Item " & $iI & "-2"
  Next
  _GUICtrlListView_AddArray($idListview, $aItems)

  While 1
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop
      Case $idButton1
        _SendMessage($idListview, $LVM_DELETEALLITEMS)
        Sleep(5000)
        _GUICtrlListView_AddArray($idListview, $aItems)
     EndSwitch
  WEnd
EndFunc   ;==>Example

 

Link to comment
Share on other sites

6 hours ago, Nine said:

You may have had a glitch in you code because it is working for me :

I confirmed your code works. But mine still doesn't. I just took @pixelsearch's code above and replaced GUICtrlCreateListView with _GUICtrlListView_Create.
 

#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <MsgBoxConstants.au3>
#include <SendMessage.au3>
#include <WindowsConstants.au3>

Global $idListView, $idLabel, $iInc = 1
Global $aArray[3][3] = _
   [["item1", "c_12", "c_13"], _
    ["item2", "c_22", "c_23"], _
    ["item3", "c_32", "c_33"]]

Example()

Func Example()
    $hGUI = GUICreate("listview items", 220, 200)
;    $idListView = GUICtrlCreateListView("col1  |col2|col3  ", 10, 10, 200, 150)

    $idListView = _GUICtrlListView_Create($hGUI, "col1  |col2|col3  ", 10, 10, 200, 150)

    Local $idButton = GUICtrlCreateButton("Click me", 25, 170, 70, 20)
    Create_Both()

    GUISetState(@SW_SHOW)

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop

            Case $idButton
                MsgBox($MB_SYSTEMMODAL, "id Label #" & $iInc & " = " & $idLabel, _
                    "Now deleting label & LV content, then recreating both")
                _SendMessage(GUICtrlGetHandle($idListView), $LVM_DELETEALLITEMS)

                Sleep(1000) ; just to show LV is empty on screen
                GUICtrlDelete($idLabel)
                $iInc += 1
                Create_Both()
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func Create_Both()
    _GUICtrlListView_AddArray($idListView, $aArray)
    $idLabel = GUICtrlCreateLabel("Label #" & $iInc, 130, 173, 70, 20)
EndFunc   ;==>Create_Both

 

Edited by CYCho
Link to comment
Share on other sites

@CYCho: your last code doesn't work because of this line : GUICtrlGetHandle($idListView)

#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <MsgBoxConstants.au3>
#include <SendMessage.au3>
#include <WindowsConstants.au3>

Global $hListView, $idLabel, $iInc = 1
Global $aArray[3][3] = _
   [["item1", "c_12", "c_13"], _
    ["item2", "c_22", "c_23"], _
    ["item3", "c_32", "c_33"]]

Example()

Func Example()
    $hGUI = GUICreate("listview items", 220, 200)
    $hListView = _GUICtrlListView_Create($hGUI, "col1  |col2|col3  ", 10, 10, 200, 150) 

    Local $idButton = GUICtrlCreateButton("Click me", 25, 170, 70, 20)
    Create_Both()

    GUISetState(@SW_SHOW)

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop

            Case $idButton
                MsgBox($MB_SYSTEMMODAL, "id Label #" & $iInc & " = " & $idLabel, _
                    "Now deleting label & LV content, then recreating both")
                _SendMessage($hListView, $LVM_DELETEALLITEMS)

                Sleep(1000) ; just to show LV is empty on screen
                GUICtrlDelete($idLabel)
                $iInc += 1
                Create_Both()
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func Create_Both()
    _GUICtrlListView_AddArray($hListView, $aArray)
    $idLabel = GUICtrlCreateLabel("Label #" & $iInc, 130, 173, 70, 20)
EndFunc   ;==>Create_Both

_GUICtrlListView_Create() returns a handle, not a control id. So GUICtrlGetHandle($hListView) returns 0 and SendMessage fails.

Edit: this little "how-to-name variables" issue is interesting and shows why it's important to give a good name to each variable. If the variables were named correctly in all these scripts, i.e.

$idListview = GUICtrlCreateListView(...)
or
$hListView = _GUICtrlListView_Create(...)

Then one could really be alerted when typing :

GUICtrlGetHandle($hListView) ; did I just handled on handle ?

 

Edited by pixelsearch
Link to comment
Share on other sites

Yep, naming is important.  But my humble advice is that error handling is what makes a difference.  When you do not know that a statement has failed, how can you intervene to solve an unknown issue.  One suggestion to all developers,  better put more error handling than less.  Your code may looks bigger but it will reduce making the wrong assumptions and solve a lot of unpredictable behaviors.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...