Russian Qt Forum

Qt => Model-View (MV) => Тема начата: kambala от Сентябрь 03, 2011, 14:35



Название: унаследовать QListView/QTableView от своего класса
Отправлено: kambala от Сентябрь 03, 2011, 14:35
Здравствуйте. Появилась необходимость использовать некоторый набор своей логики в двух разных классах-наследниках QListView и QTableView. Эту логику вынес в отдельный класс:
Код
C++ (Qt)
class ItemView : public QAbstractItemView // тут по идее нужен virtual
{
   Q_OBJECT
 
public:
   ItemView(QWidget *parent) : QAbstractItemView(parent) { connect(this, SIGNAL(clicked(const QModelIndex &)), SIGNAL(currentItemChanged(const QModelIndex &))); }
 
signals:
   void currentItemChanged(const QModelIndex &newIndex);
 
protected:
virtual void keyPressEvent(QKeyEvent *event)
{
   QModelIndex oldIndex = currentIndex();
   QAbstractItemView::keyPressEvent(event); // раньше, когда метод находился в классе-наследнике QListView, то вызывался просто для QListView
   int key = event->key();
   if (key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Home || key == Qt::Key_End)
       if (oldIndex != currentIndex())
           emit currentItemChanged(currentIndex());
}
};

Но как теперь сделать так, чтоб наследники QListView и QTableView использовали этот класс в качестве "базового" вместо обычного QAbstractItemView? Почитал про виртуальное наследование (раньше никогда с ним не сталкивался), но, похоже, не слишком разобрался в нем.
Код
C++ (Qt)
class MyListView : public ItemView, public QListView
{
   Q_OBJECT
public:
   explicit MyListView(QWidget *parent = 0) : ItemView(parent), QListView(parent) {}
...
};


Название: Re: унаследовать QListView/QTableView от своего класса
Отправлено: twp от Сентябрь 03, 2011, 15:17
вообще то, насколько мне известно, наследоваться от двух классов, которые являются предками QObject нельзя. И вообще к чему такой наворот? Нельзя что ли кинуть фильтр событий или кастомизировать QListView и QTableView?


Название: Re: унаследовать QListView/QTableView от своего класса
Отправлено: kambala от Сентябрь 03, 2011, 15:34
Нельзя что ли кинуть фильтр событий или кастомизировать QListView и QTableView?
точно, а я и забыл про фильтр. спасибо!

а кастомизация напрямую не подходит т.к. это будет copy-paste программирование :)


Название: Re: унаследовать QListView/QTableView от своего класса
Отправлено: kambala от Сентябрь 03, 2011, 16:31
а через фильтр не получается так просто сделать. мне нужно (см. код выше), чтобы событие нажатия клавиши сначала отправилось виджету (чтобы текущий элемент изменился), затем сравнить новый текущий элемент со старым (чтобы предотвратить лишний вызов функции) и только если элемент другой - вызвать нужный метод. попытался сделать вот так:
Код
C++ (Qt)
ui.gearListView->installEventFilter(this);
...
bool ItemsViewerDialog::eventFilter(QObject *obj, QEvent *event)
{
   if (event->type() == QEvent::KeyPress /*|| event->type() == QEvent::MouseButtonPress*/)
   {
       QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
       QAbstractItemView *itemView = static_cast<QAbstractItemView *>(obj);
       QModelIndex oldIndex = itemView->currentIndex();
       qApp->sendEvent(obj, event); // send keyPressEvent immediately
 
       int key = keyEvent->key();
       if (key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Home || key == Qt::Key_End)
           if (oldIndex != itemView->currentIndex())
               itemSelected(itemView->currentIndex());
       return true;
   }
   return QDialog::eventFilter(obj, event);
}
но это мало того, что выглядит как чистой воды хак, так еще и крашится (postEvent тоже) :) а еще надо обрабатывать клик мышкой по элементу.

теперь думаю возвращать false из фильтра и использовать какой-то способ отложенного вызова метода, в который надо запихнуть все, что идет после получения oldIndex, типа QTimer::singleShot() вкупе с QMetaObject::invokeMethod() (не знаю как, но надо), но почему-то у меня такое ощущение, будто я изобретаю синхрофазотрон для езды по улицам :D


Название: Re: унаследовать QListView/QTableView от своего класса
Отправлено: twp от Сентябрь 03, 2011, 16:52
да, было бы классно сделать что-то типа такого
Код:
template <class T>
class ItemView : public T
{
    Q_OBJECT
 
public:
    ItemView(QWidget *parent) : T(parent) { connect(this, SIGNAL(clicked(const QModelIndex &)), SIGNAL(currentItemChanged(const QModelIndex &))); }
 
signals:
    void currentItemChanged(const QModelIndex &newIndex);
 
protected:
    virtual void keyPressEvent(QKeyEvent *event)
    {
        QModelIndex oldIndex = currentIndex();
        T::keyPressEvent(event);
        int key = event->key();
        if (key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Home || key == Qt::Key_End)
            if (oldIndex != currentIndex())
                emit currentItemChanged(currentIndex());
    }
};
а потом просто
Код:
    ...
    ItemView<QListView> *view = new ItemView<QListView>(parentWidget);
    ItemView<QTableView> *table = new ItemView<QTableView>(parentWidget);
Но moc не пропустит  :(
А вот
qApp->sendEvent(obj, event);
лучше не делать, может просто дать отработать
QDialog::eventFilter(obj, event);

Код:
bool ItemsViewerDialog::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyPress /*|| event->type() == QEvent::MouseButtonPress*/)
    {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        QAbstractItemView *itemView = static_cast<QAbstractItemView *>(obj);
        QModelIndex oldIndex = itemView->currentIndex();
        QDialog::eventFilter(obj, event);
 
        int key = keyEvent->key();
        if (key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Home || key == Qt::Key_End)
            if (oldIndex != itemView->currentIndex())
                itemSelected(itemView->currentIndex());
        return true;
    }
     return QDialog::eventFilter(obj, event);
}


Название: Re: унаследовать QListView/QTableView от своего класса
Отправлено: kambala от Сентябрь 03, 2011, 17:30
может просто дать отработать
QDialog::eventFilter(obj, event);
нет, не помогает :( как будто этого вызова и нету

добавлено: в общем, вот так (через задницу) работает, но мышиные события не ловятся (хотелось бы конечно, но тут можно сигналом clicked() обойтись) - в момент нажатия мышкой в фильтр приходит только paintEvent и timerEvent (в гугле нашел, что мышиные события могут идти в widget->viewport(), но это уж совсем хаком будет):
Код
C++ (Qt)
class ItemsViewerDialog : public QDialog
{
   Q_OBJECT
   ...
protected:
   bool eventFilter(QObject *obj, QEvent *event);
 
private slots:
   void itemSelected(const QModelIndex &index) { ... }
   void selectItem();
 
private:
   Ui::ItemsViewerWidget ui;
 
   QModelIndex _oldIndex;
   bool _wasAllowedKeyPressed;
   QAbstractItemView *_itemView;
};
Код
C++ (Qt)
bool ItemsViewerDialog::eventFilter(QObject *obj, QEvent *event)
{
   if (event->type() == QEvent::KeyPress || event->type() == QEvent::MouseButtonPress)
   {
       _itemView = static_cast<QAbstractItemView *>(obj);
       _oldIndex = _itemView->currentIndex();
 
       if (event->type() == QEvent::KeyPress)
       {
           int key = static_cast<QKeyEvent *>(event)->key();
           _wasAllowedKeyPressed = key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Home || key == Qt::Key_End;
       }
       else
           _wasAllowedKeyPressed = true;
 
       QTimer::singleShot(0, this, SLOT(selectItem()));
       return false;
   }
   return QDialog::eventFilter(obj, event);
}
 
void ItemsViewerDialog::selectItem()
{
   if (_wasAllowedKeyPressed && _oldIndex != _itemView->currentIndex())
       itemSelected(_itemView->currentIndex());
}


Название: Re: унаследовать QListView/QTableView от своего класса
Отправлено: kambala от Сентябрь 03, 2011, 19:48
решил все-таки вернуться к китайскому стилю, и оказалось, что для QTableView все не так просто из-за span'ов, так что хотя бы полностью дублируемого кода не будет :) но все равно хотелось бы иметь возможность вносить свои "расширения" (категории :) ) в существующие классы...


Название: Re: унаследовать QListView/QTableView от своего класса
Отправлено: Akon от Сентябрь 04, 2011, 01:29
Цитировать
да, было бы классно сделать что-то типа такого
Код:
template <class T>
class ItemView : public T
{
    Q_OBJECT
 
public:
    ItemView(QWidget *parent) : T(parent) { connect(this, SIGNAL(clicked(const QModelIndex &)), SIGNAL(currentItemChanged(const QModelIndex &))); }
 
signals:
    void currentItemChanged(const QModelIndex &newIndex);
 
protected:
    virtual void keyPressEvent(QKeyEvent *event)
    {
        QModelIndex oldIndex = currentIndex();
        T::keyPressEvent(event);
        int key = event->key();
        if (key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Home || key == Qt::Key_End)
            if (oldIndex != currentIndex())
                emit currentItemChanged(currentIndex());
    }
};
а потом просто
Код:
    ...
    ItemView<QListView> *view = new ItemView<QListView>(parentWidget);
    ItemView<QTableView> *table = new ItemView<QTableView>(parentWidget);
Но moc не пропустит

Миксин можно навернуть, если уберете Q_OBJECT, но сами понимаете - поддержки метасистемы вам не видать.

Множественное наследование можно заменить вложением.