LINUX.ORG.RU

[Qt] Жуткая проблема: не могу правильно обработать выбор строки в QListView


0

2

Уже третий день бъюсь над простейшей задачей.

Есть меня QListView. Нужно сделать слот, который бы срабатывал при каждом переключении курсора на другую строку. Ну, то есть, кликнул человек мышкой на нужную строку - выполнился слот. Нажал человек клавишу вверх/вниз, выбралась другая строка, - нужно вызвать тот же слот.

Задача проста. А правильного решения сделать не могу, и вот почему.

Клик мышки по строке списка можно отловить с помощью сигнала clicked() объекта QListView. Он работает правильно, без нареканий.

Но нам еще нужно отлавливать движение клавишами по списку. А это движение можно отловить только с помощью сигнала select-модели списка, именуемого currentRowChanged(). Прототип этого сигнала:

currentRowChanged (const QModelIndex & current, const QModelIndex & previous)

Получается, что слот-обработчик нужно подключать к двум сигналам - к clicked() и currentRowChanged(). Но тогда при выборе нужной записи мышкой, слот срабатывает два (!) раза - один раз от клика, и второй раз из-за того, что засветка переместилась на другую строку. А мне нужно чтоб слот вызывался один раз! Ведь клик-то был один.


Но и это еще не всё. Сигнал currentRowChanged() для QListView, в котором не выбрана ни одна запись, вырабатывается очень хитрым способом. У этого сигнала два параметра - QModelIndeх выбранной записи и QModelIndeх предыдущей записи. Мы для простоты получаем из этих индексов номера записей методом row(). Закономерность генерируемых сигналов и их параметров следующая:

  • Если кликнуть по записи с номером 0 (верхняя запись), то сгенерируется один сигнал с параметрами: (0, -1).
  • Если кликнуть по записи с номером, отличным от нуля то сгенерируется два (!) сигнала с параметрами: (0, -1) и (номер_записи, 0).
  • Если выбрать нужную запись программно (через selection model), то сгенерируется один сигнал с параметрами: (номер_записи, -1).

Из-за такого дикого поведения, слот-обработчик может быть вызван три (!) раза (один раз сигналом clicked(), два раза сигналом currentRowChanged(), если ткнуть на любую запись кроме первой). А мне нужно, чтоб слот был вызван один раз.

Я решил отказаться от сигнала clicked(), так как currentRowChanged() в моей задаче «перекрывает» его функциональность. Но так как этот currentRowChanged() ведет себя дико - генерируется два раза при выборе не первой строки в списке, - я не могу его толком использовать.


Вопрос: как мне отследить выбор строки в списке QListView так, чтобы слот срабатывал только один раз? Нужно учесть, что выбирать пользователь может как кликом мышкой, так и движением засветки клавишами клавиатуры.

QListView не имеет метода currentRowChanged
Это у QListWidget.
Или я что-то не понял?

zJes ★★ ()
Ответ на: комментарий от zJes

> QListView не имеет метода currentRowChanged

> сигнала select-модели списка

rival ★★ ()
Ответ на: комментарий от rival

Упс, тут просто столько букв, что я потерялся. :)

zJes ★★ ()
Ответ на: комментарий от Dendy

Похоже один-в-один, если так, то эта ошибка закрыта в Qt 4.7.0, вам стоит обновиться.

Dendy ★★★★★ ()
#include <QApplication>
#include <QListView>
#include <QFileSystemModel>
#include <QDebug>

class DebugObject : public QObject {
  Q_OBJECT

public slots:
  void currentRowChanged(const QModelIndex & current, const QModelIndex & previous) {
    qDebug() << current << previous;
  }
};

int main(int argc, char **argv) {
  QApplication app(argc, argv);
  
  QFileSystemModel model;
  model.setRootPath(QDir::currentPath());
  
  QListView view;
  view.setModel(&model);
  view.setRootIndex(model.index(QDir::currentPath()));
  view.show();

  DebugObject obj;

  obj.connect(view.selectionModel(),
              SIGNAL(currentRowChanged(const QModelIndex, const QModelIndex)), 
              SLOT(currentRowChanged(const QModelIndex, const QModelIndex)));

  return app.exec();
}

#include "moc_main.cpp"
Using Qt version 4.7.2 in /usr/lib/qt4

Ошибка не проявляется.

anonymous ()
Ответ на: комментарий от Dendy

> Похоже один-в-один, если так, то эта ошибка закрыта в Qt 4.7.0, вам стоит обновиться.

Да, глюк похож, но там только часть глюка описана.

У меня Qt 4.6.3. А я пишу программу так чтобы работала в Qt 4.5. И что теперь, те у кого Qt ниже 4.7 будут плеваться на программу? Они же не библиотеку будут плеваться, а на вполне конкретную программу.

Но походу проблема есть и в 4.7.x ветке. Проблема в том, что в QListView автоматически «скрыто» выделяется первая запись. И генерируется сигнал. Он же генерируется и если свернуть-развернуть окно.

Проблема в том, что такое поведение не позволяет правильно отследить действия пользователя - пользователь строку в списке не выбирал, а она на автопилоте «скрыто» выбрана, да еще и сигнал сгенерировался.

webhamster ()
Ответ на: комментарий от webhamster

> Он же генерируется и если свернуть-развернуть окно.

В примере выше у меня не воспроизводится.

anonymous ()
Ответ на: комментарий от webhamster

> Проблема в том, что в QListView автоматически «скрыто» выделяется первая запись.

Такое есть, но это можно заигнорить, раз случается только один раз (у меня).

Вообще, можно отлавливать, например, selection (он скрыто не должно выделяться):
void selectionChanged ( const QItemSelection & selected, const QItemSelection & deselected )
Либо activated, в зависимости от того, что нужно. В конце концов, можно переопределить keyPressEvent и отлавливать вверх-вниз, аналогично с мышкой.

anonymous ()
Ответ на: комментарий от anonymous

> Вообще, можно отлавливать, например, selection (он скрыто не должно выделяться):

> void selectionChanged ( const QItemSelection & selected, const QItemSelection & deselected )

Ага, попробую так, может поможет.

Не совсем понял, как превращать QItemSelection в QModelIndex выбранной строки. Через метод indexes(), и там брать первый элемент в списке чтоли?

webhamster ()
Ответ на: комментарий от webhamster

> Не совсем понял, как превращать QItemSelection в QModelIndex выбранной строки. Через метод indexes(), и там брать первый элемент в списке чтоли?

Именно так. Selection, вообще говоря, может быть пустым, а может содержать и больше одного индекса (правда это настраивается):
http://doc.qt.nokia.com/latest/qabstractitemview.html#SelectionMode-enum

anonymous ()
Ответ на: комментарий от BredoGen

> Qt 4.6.2. Бага нет.

Да есть он, просто ты его не видишь. Не зря же фиксили в 4.7.x.

webhamster ()
Ответ на: комментарий от webhamster
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QStringList list;
    list    << "first"
            << "second"
            << "third";
    QAbstractItemModel *model = new QStringListModel(list);
    QListView *view = new QListView;
    view->setModel(model);

    connect(view->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), SLOT(debug(QModelIndex,QModelIndex)));

    setCentralWidget(view);
}

void MainWindow::debug(QModelIndex f, QModelIndex s)
{
    qDebug() << f.row() << ", " << s.row();
}

Первый сигнал высылается при запуске, остальные как и положено при переходе (никаких двойных сигналов не замечено).

0, -1 
1, 0 
2, 1 

BredoGen ()
Ответ на: комментарий от BredoGen

Ты сделал предварительно альт-таб на другое окно, чтобы потерять focus?

anonymous ()
Ответ на: комментарий от BredoGen

> Первый сигнал высылается при запуске, остальные как и положено при переходе (никаких двойных сигналов не замечено).

Если у тебя список неактивен, и у него нет выбранной строки (вот ты смотришь на список, и у него ни одна строка не выбрана, даже пунктиром не обведена, и сам список синенькой рамочкой не обведен), то тогда при клике на _не_первую_ запись у тебя сгенерируются два сигнала.

Первый - потому что засветка «скрыто» установится на самую верхнюю запись (то же самое что у тебя происходит «при запуске»). Второй - потому что ты ткнул не на самую верхнюю запись, и засветка таки должна туда перескочить.

У меня в такое «чистое, невыбранное» состояние попадает QListView потому, что возможны ситуации, когда список вообще не содержит записей, или его содержимое кардинально меняется так, что меняется и Item Model,и приходится сбрасывать Selection Model.

webhamster ()

Мне одному кажется, что тут что-то не так с программой? Почему автоматический выбор первого элемента может стать проблемой? Может это не совсем походящий виджет выбран?

Судя по всему, можно поставить проверку на previous.isValid() и не выбирать ничего программно (или же учитывать это в проверке).

unC0Rr ★★★★★ ()
Ответ на: комментарий от unC0Rr

> Мне одному кажется, что тут что-то не так с программой?

Да


> Почему автоматический выбор первого элемента может стать проблемой?


Потому что в случае клиент-серверной архитектуры, если ответ приходит медленно, автовыбор первого элемента, который пользователь не выбирал, является проблемой. Проблема в лаге, в избытке траффика и потенциально - в нарушении протокола обмена.

Другой вариант - вам надо помнить историю выбираемых пользователем записей. Пользователь не выбирал первую запись, а Qt вам скажет что нажимал.

Проверка на previous.isValid() не подходит, так как при клике на первую запись неясно, выбрана ли действительно первая запись, или это промежуточное «скрытое» нажатие.

webhamster ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.