c++qtqlistwidgetqlistwidgetitem

QListWidget shifts when user selects an item


I have a peculiar issue. I have a QListWidget of 1000 QListWidgetItem. I have a search textbox that triggers the following code whenever someone starts typing in it.

QRegExp regExSearch(searchText, Qt::CaseInsensitive, QRegExp::RegExp);
for (QListWidgetItem * currIt : allItems)
{
    currIt->setHidden( !(currIt->text().contains(regExSearch)) );
}

What ends up being is once my search is performed, I will have a QListWidget with 600 items hidden, and 400 shown. Whenever I click on an item, the entire list just jumps down a few rows so that my selection is not even visible on the screen.

I've confirmed that it is the setHidden that seems to be causing it. If I just highlight found rows, without hiding/showing items, the selection does not cause the list to scroll down.

As such, I am wondering what am I missing? Which function to I call, to make sure that my QListWidget does not shift whenever I select an item?

Working example below:

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QListWidget>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QListWidget * myListWidget = new QListWidget();

    for (int i = 0; i <= 1000; ++i)
    {
        QListWidgetItem * myItem = new QListWidgetItem(myListWidget);
        QString text("");
        for (int i = 0; i <= 100; ++i)
        {
            text.append("W");
        }
        myItem->setText(text + QString::number(i));
    }

    for (int i = 0; i <= 1000; ++i)
    {
        if (i%2)
            myListWidget->item(i)->setHidden(true);
    }

    setCentralWidget(myListWidget);
}

MainWindow::~MainWindow()
{
    delete ui;

}

I have basically replicated and isolated the issue to being horizontal scrollbars. In my original application AND in this mcve, if you select an item (for example at index 314) the list will jump down. However, if I resize the list to not have horizontal scrollbars, then it does not shift.

So now that I know the issue is with having horizontal scrollbar, I am still not sure how to go about preventing the jump from happening.

UPDATE:

I have attempted to use SIGNAL-SLOT as such:

connect(myListWidget, SIGNAL(itemClicked(QListWidgetItem*)), myListWidget, 
            SLOT(scrollToItem(const QListWidgetItem*,QAbstractItemView::ScrollHint)));

I have also attempted to create my own slot where I explicitly specify that the item should be visible:

void MainWindow::scrollToItem(QListWidgetItem * item)
{
    std::cout << "Scrolling to item." << std::endl;
    myListWidget->scrollToItem(item, QAbstractItemView::EnsureVisible);
}

However, it still jumps down and out of view! It does work if I set the ScrollHint to PositionAtCenter! BUT, from a user experience perspective, it is not a desired behaviour to have the list shift every time they click an item (even if that item is now in the center of the screen). Do I have any other options?


Solution

  • Using QListWidget::scrollToItem you can rescroll your list so that your selected item is visible.

    scrollToItem also takes a ScrollHint which allows you to specify where the QListWidget scrolls to (the default is EnsureVisible)

    • EnsureVisible: scroll to ensure that the item is visible.
    • PositionAtTop: scroll to position the item at the top of the viewport.
    • PositionAtBottom: scroll to position the item at the bottom of the viewport.
    • PositionAtCenter: scroll to position the item at the center of the viewport.

    Full working example below:

    #include <QApplication>
    #include <QMainWindow>
    #include <QVBoxLayout>
    #include <QPushButton>
    #include <QListWidget>
    #include <QLineEdit>
    #include <QRegExp>
    
    int main(int argc, char** argv)
    {
        QApplication* app = new QApplication(argc, argv);
        QMainWindow*  window = new QMainWindow();
    
        QWidget widget;
        QVBoxLayout layout(&widget);
    
        QLineEdit edit;
        layout.addWidget(&edit);
    
        QListWidget list;
        layout.addWidget(&list);
    
        for (int i = 0; i < 1000; ++i)
        {
            QListWidgetItem* item = new QListWidgetItem(QString::number(i));
            list.addItem(item);
        }
    
        QObject::connect(&edit, &QLineEdit::textChanged, [&](const QString& text)
            {
                QRegExp re(text, Qt::CaseInsensitive, QRegExp::RegExp);
                for(int i = 0; i < list.count(); ++i)
                {
                    QListWidgetItem* item = list.item(i);
                    item->setHidden(!(item->text().contains(re)));
                }
    
                auto selected = list.selectedItems();
                if (selected.size() == 1)
                {
                    list.scrollToItem(selected.front());
                }
            });
    
        window->setCentralWidget(&widget);
        window->show();
        return app->exec();
    }