Search code examples
listviewwinapic++builder

Items inserted into a TListView in Tile view always appear at the bottom of the list


I have a VCL forms application, written in C++Builder XE6, that contains a TListView in Tile view.

Since the TListView VCL component doesn't have a Tile view option, I use a TListView with ViewStyle=vsReport in the form designer and switch it to Tile view in code:

void __fastcall TForm1::FormShow (TObject *Sender)
{
    TLVTileViewInfo tvi = {};

    tvi.cbSize = sizeof(tvi);
    tvi.dwMask = LVTVIM_COLUMNS;
    tvi.cLines = ListView1->Columns->Count - 1;

    ListView_SetTileViewInfo(ListView1->Handle, &tvi);

    ListView_SetView(ListView1->Handle, LV_VIEW_TILE);
}

The TListView also has Checkboxes=true, GroupView=false, RowSelect=true, ShowWorkAreas=false and SortType=stNone.

The TListView has the following OnInsert event handler:

void __fastcall TForm1::ListView1Insert (TObject *Sender, TListItem *Item)
{
    TLVTileInfo ti = {};

    Item->Caption = " ";    // ComCtl32.dll throws an access violation exception when caption is empty.

    ti.cbSize    = sizeof(ti);
    ti.iItem     = Item->Index;
    ti.cColumns  = ListView1->Columns->Count - 1;
    ti.puColumns = new unsigned[ti.cColumns];
    ti.piColFmt  = new int[ti.cColumns];

    try
    {
        for (unsigned i = 0; i < ti.cColumns; i++)
        {
            ti.puColumns[i] = 1 + i;
            ti.piColFmt [i] = LVCFMT_LEFT;
        }

        ListView_SetTileInfo(ListView1->Handle, &ti);
    }
    __finally
    {
        delete[] ti.puColumns;
        delete[] ti.piColFmt;
    }
}

I insert an item into the TListView, for example at the top:

TListItem* Item = ListView1->Items->Insert(0);

The item is inserted at the top of the list (Item->Index is 0 after the insertion), but appears at the bottom of the list view on screen.

When I delete an item, the list view shows an empty space where the tile used to be. I fixed this with the following code after the deletion:

ListView_Arrange(ListView1->Handle, LVA_DEFAULT);

This removes the empty space from the list view.

Unfortunately, this has no effect when called after the insertion of an item. Newly inserted items remain displayed at the bottom of the list view.

If I insert an item into the list view in Report view, all works as expected. Just not when in Tile view.

How do I get the list view to display the items in the correct order (i.e. ascending order of the Index property of the items) in Tile View?

Update

As it turns out, switching the view to any other type and back to LV_VIEW_TILE causes the list view to arrange the items in the proper order.

This can be done after inserting the new item:

void __fastcall TForm1::Button1Click (TObject *Sender)
{
    ListView1->Items->BeginUpdate();    // Disable repainting list view.

    try
    {
        TListItem* Item = ListView1->Items->Insert(0);

        Item->Caption = "New item";

        ListView_SetView(ListView1->Handle, LV_VIEW_LIST);  // Can be any LV_VIEW_... value other than LV_VIEW_TILE.
        ListView_SetView(ListView1->Handle, LV_VIEW_TILE);
    }
    __finally
    {
        ListView1->Items->EndUpdate();  // Enable repainting list view.
    }
}

Solution

  • Try this code as a work around, does what you want and you won't even notice the switch from list to tile views.

        void __fastcall TForm3::Button1Click(TObject *Sender)
        {
        ViewList();                             //  set ViewStyle to  LV_VIEW_LIST
        TListItem *item;
        item = ListView1->Items->Insert(2);     //  insert it @ item index 2
                                                //  or ListView1->Items->Insert(ListView1->Selected->Index);
        item->Caption = "Hello 1";
        ViewTile();                             //  restore view to LV_VIEW_TILE 
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm3::ViewTile()
        {
        //set view to Tile View
        TLVTileViewInfo tvi = {};
    
        tvi.cbSize = sizeof(tvi);
        tvi.dwMask = LVTVIM_COLUMNS;
        tvi.cLines = ListView1->Columns->Count - 1;
    
        ListView_SetTileViewInfo(ListView1->Handle, &tvi);
    
        ListView_SetView(ListView1->Handle, LV_VIEW_TILE);
    
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm3::ViewList()
        {
        // restore view to List View aka vsReport
        TLVTileViewInfo tvi = {};
    
        tvi.cbSize = sizeof(tvi);
        tvi.dwMask = LVTVIM_COLUMNS;
        tvi.cLines = ListView1->Columns->Count - 1;
    
        ListView_SetTileViewInfo(ListView1->Handle, &tvi);
    
        ListView_SetView(ListView1->Handle, LV_VIEW_LIST);
        }
    

    Add your ListView_Arrange(ListView1->Handle, LVA_DEFAULT); to your Delete1Click event of your popup menu (you already do that but shown here for others).

        void __fastcall TForm3::Delete1Click(TObject *Sender)
    {
    ListView1->DeleteSelected();
    ListView_Arrange(ListView1->Handle, LVA_DEFAULT);
    }