3
votes

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?

1
make sure you don't have slots connected to the list that may be doing something when the an item is clicked or selection changes.Ronaldo Nazarea
@RonaldoNazarea Yup! As far as I can tell, the only event i have is item double click. Which, interestingly enough, works. I have a new widget open on double click. And in that case, the QListWidget does not scroll...Metal Wing
are you sure the entire list if jumping down? WIthout having a mcve it is difficult to draw conclusions, but it sounds suspiciously like your selection is jumping up, as QListWidgetItems above your selected item are hidden, thereby causing the visible list to become shorter, and therefore your selected item is a shorter distance from the top, and jumps out of the visible area that you are currently scrolled toSteve Lorimer
@SteveLorimer I find that surprising too, but yes, it jumps down. If I select item 399, the list scrolls so I see items 500-600. If i scroll up, I see my selection (still active and everything). I am debating if I should try to intercept a mouse event at this point. But then I am not sure what function I can call to keep the list in place... What's worse, is that I tried writing a mcve that just generates a list of 1-1000 and hides items at index%2 - and that works fine. Nothing jumps :/Metal Wing
@SteveLorimer Thank you for the working example. My apologies, I haven't been able to run it yet (for some reason vcxserv refuses to copy paste to the remote machine I am working on). However, I did update with my own code example. The issue appears to be with horizontal scrollbars in my case. I've been able to prove it with both - my original application AND the mcve. As soon as I resize to not have horizontal scroll - everything works. Now I need to address that somehow.Metal Wing

1 Answers

1
votes

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();
}