12

QSqlTableModelとを使用してQTableViewSQLiteデータベーステーブルを表示しています。

テーブルを1秒ごとに自動更新してもらいたいです(非常に大きなテーブルにはなりません-数百行)。そして、私はこれを行うことができます-そのように:

QTimer *updateInterval = new QTimer(this);
updateInterval->setInterval(1000);
updateInterval->start();
connect(updateInterval, SIGNAL(timeout()),this, SLOT(update_table()));

...

void MainWindow::update_table()
{
    model->select(); //QSqlTableModel*
    sqlTable->reset(); //QTableView*
}

しかし、これは私が持っている選択を削除するので、選択は最大1秒しか続きません。GUIの別のペインは何を選択するかによって異なるため、これは煩わしいことです。何も選択されていない場合は、説明のスプラッシュページにリセットされます。

次に、選択した行番号を取得し、テーブルをリセットしてから、その行を選択する、ややハッキーなアプローチを試しました。ただし、選択した行はテーブルへの追加に基づいて上下に移動できるため、これも機能しません。

私は他のクラスがdataChanged()理想的な信号を持っていることを知っています。

(コマンドラインの使用法またはプログラムの他のインスタンスからの)データベースへの変更を反映し、現在の選択を維持するためにテーブルを更新する方法を知っている人はいますか?

現在の選択からデータを取得し、リセット後に同じ行を検索して再選択できることはわかっていますが、これは逆効果で問題の悪い解決策のようです。

編集:解決策の現在の試み:

void MainWindow::update_table()
{    

    QList<QModelIndex> selection = sqlTable->selectionModel()->selection().indexes();
    QList<int> selectedIDs;
    bool somethingSelected = true;

    for(QList<QModelIndex>::iterator i = selection.begin(); i != selection.end(); ++i){
        int col = i->column();
        QVariant data = i->data(Qt::DisplayRole);

    if(col == 0) {
            selectedIDs.append(data.toInt());
        }
    }

    if(selectedIDs.empty()) somethingSelected = false;

    model->select();
    sqlTable->reset();

    if(somethingSelected){
        QList<int> selectedRows;

        int rows = model->rowCount(QModelIndex());
        for(int i = 0; i < rows; ++i){
            sqlTable->selectRow(i);
            if(selectedIDs.contains(sqlTable->selectionModel()->selection().indexes().first().data(Qt::DisplayRole).toInt())) selectedRows.append(i);
    }

    for(QList<int>::iterator i = selectedRows.begin(); i != selectedRows.end(); ++i){
        sqlTable->selectRow(*i);
    }
}
}

さて、これは多かれ少なかれ今は機能します...

4

1 に答える 1

10

実際の取引は、クエリ結果の主キーです。QSqlTableModel::primaryKey()QtのAPIは、列のリストへのかなり遠回りなルートを提供します。の結果はprimaryKey()でありQSqlRecord、それを繰り返し処理して、field()sそれらが何であるかを確認できます。から適切なクエリを構成するすべてのフィールドを検索することもできますQSqlTableModel::record()。後者で前者を見つけて、クエリを構成するモデル列のリストを取得します。

クエリに主キーが含まれていない場合は、自分で主キーを設計し、何らかのプロトコルを使用して提供する必要があります。たとえば、primaryKey().isEmpty()trueの場合、モデルによって返される最後の列を主キーとして使用するように選択できます。任意のクエリの結果をキーイングする方法を理解するのはあなた次第です。

選択した行は、主キー(キーを構成するセルの値のリスト--a)によって簡単にインデックスを付けることができますQVariantList。このため、デザインが壊れていなければ、カスタム選択モデル( )を使用できます。QItemSelectionModelのような主要なメソッドisRowSelected()は仮想ではなく、それらを再実装することはできません:(。

代わりに、データのカスタムを提供することで選択を模倣するプロキシモデルを使用できますQt::BackgroundRole。モデルはテーブルモデルの上に配置され、選択されたキーの並べ替えられたリストを保持します。プロキシモデルdata()が呼び出されるたびに、基になるクエリモデルから行のキーを取得し、並べ替えられたリストでそれを検索します。最後に、アイテムが選択されている場合は、カスタムのバックグラウンドロールを返します。に関連する比較演算子を作成する必要がありますQVariantListQItemSelectionModelこの目的で使用できる場合は、この機能をの再実装に入れることができますisRowSelected()

クエリモデルからキーを抽出するための特定のプロトコルにサブスクライブするため、モデルは一般的です。つまり、を使用しprimaryKey()ます。

主キーを明示的に使用する代わりに、モデルが主キーをサポートしている場合は、永続インデックスを使用することもできます。残念ながら、少なくともQt 5.3.2まではQSqlTableModel、クエリが再実行されたときに永続インデックスを保持しません。したがって、ビューがソート順を変更するとすぐに、永続インデックスは無効になります。

以下は、そのような獣を実装する方法の完全に解決された例です。

例のスクリーンショット

#include <QApplication>
#include <QTableView>
#include <QSqlRecord>
#include <QSqlField>
#include <QSqlQuery>
#include <QSqlTableModel>
#include <QIdentityProxyModel>
#include <QSqlDatabase>
#include <QMap>
#include <QVBoxLayout>
#include <QPushButton>

// Lexicographic comparison for a variant list
bool operator<(const QVariantList &a, const QVariantList &b) {
   int count = std::max(a.count(), b.count());
   // For lexicographic comparison, null comes before all else
   Q_ASSERT(QVariant() < QVariant::fromValue(-1));
   for (int i = 0; i < count; ++i) {
      auto aValue = i < a.count() ? a.value(i) : QVariant();
      auto bValue = i < b.count() ? b.value(i) : QVariant();
      if (aValue < bValue) return true;
   }
   return false;
}

class RowSelectionEmulatorProxy : public QIdentityProxyModel {
   Q_OBJECT
   Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush)
   QMap<QVariantList, QModelIndex> mutable m_selection;
   QVector<int> m_roles;
   QBrush m_selectedBrush;
   bool m_ignoreReset;
   class SqlTableModel : public QSqlTableModel {
   public:
      using QSqlTableModel::primaryValues;
   };
   SqlTableModel * source() const {
      return static_cast<SqlTableModel*>(dynamic_cast<QSqlTableModel*>(sourceModel()));
   }
   QVariantList primaryValues(int row) const {
      auto record = source()->primaryValues(row);
      QVariantList values;
      for (int i = 0; i < record.count(); ++i) values << record.field(i).value();
      return values;
   }
   void notifyOfChanges(int row) {
      emit dataChanged(index(row, 0), index(row, columnCount()-1), m_roles);
   }
   void notifyOfAllChanges(bool remove = false) {
      auto it = m_selection.begin();
      while (it != m_selection.end()) {
         if (it->isValid()) notifyOfChanges(it->row());
         if (remove) it = m_selection.erase(it); else ++it;
      }
   }
public:
   RowSelectionEmulatorProxy(QObject* parent = 0) :
      QIdentityProxyModel(parent), m_roles(QVector<int>() << Qt::BackgroundRole),
      m_ignoreReset(false) {
      connect(this, &QAbstractItemModel::modelReset, [this]{
         if (! m_ignoreReset) {
            m_selection.clear();
         } else {
            for (auto it = m_selection.begin(); it != m_selection.end(); ++it) {
               *it = QModelIndex(); // invalidate the cached mapping
            }
         }
      });
   }
   QBrush selectedBrush() const { return m_selectedBrush; }
   void setSelectedBrush(const QBrush & brush) {
      if (brush == m_selectedBrush) return;
      m_selectedBrush = brush;
      notifyOfAllChanges();
   }
   QList<int> selectedRows() const {
      QList<int> result;
      for (auto it = m_selection.begin(); it != m_selection.end(); ++it) {
         if (it->isValid()) result << it->row();
      }
      return result;
   }
   bool isRowSelected(const QModelIndex &proxyIndex) const {
      if (! source() || proxyIndex.row() >= rowCount()) return false;
      auto primaryKey = primaryValues(proxyIndex.row());
      return m_selection.contains(primaryKey);
   }
   Q_SLOT void selectRow(const QModelIndex &proxyIndex, bool selected = true) {
      if (! source() || proxyIndex.row() >= rowCount()) return;
      auto primaryKey = primaryValues(proxyIndex.row());
      if (selected) {
         m_selection.insert(primaryKey, proxyIndex);
      } else {
         m_selection.remove(primaryKey);
      }
      notifyOfChanges(proxyIndex.row());
   }
   Q_SLOT void toggleRowSelection(const QModelIndex &proxyIndex) {
      selectRow(proxyIndex, !isRowSelected(proxyIndex));
   }
   Q_SLOT virtual void clearSelection() {
      notifyOfAllChanges(true);
   }
   QVariant data(const QModelIndex &proxyIndex, int role) const Q_DECL_OVERRIDE {
      QVariant value = QIdentityProxyModel::data(proxyIndex, role);
      if (proxyIndex.row() < rowCount() && source()) {
         auto primaryKey = primaryValues(proxyIndex.row());
         auto it = m_selection.find(primaryKey);
         if (it != m_selection.end()) {
            // update the cache
            if (! it->isValid()) *it = proxyIndex;
            // return the background
            if (role == Qt::BackgroundRole) return m_selectedBrush;
         }
      }
      return value;
   }
   bool setData(const QModelIndex &, const QVariant &, int) Q_DECL_OVERRIDE {
      return false;
   }
   void sort(int column, Qt::SortOrder order) Q_DECL_OVERRIDE {
      m_ignoreReset = true;
      QIdentityProxyModel::sort(column, order);
      m_ignoreReset = false;
   }
   void setSourceModel(QAbstractItemModel * model) Q_DECL_OVERRIDE {
      m_selection.clear();
      QIdentityProxyModel::setSourceModel(model);
   }
};

int main(int argc, char *argv[])
{
   QApplication app(argc, argv);
   QWidget w;
   QVBoxLayout layout(&w);

   QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
   db.setDatabaseName(":memory:");
   if (! db.open()) return 255;

   QSqlQuery query(db);
   query.exec("create table chaps (name, age, constraint pk primary key (name, age));");
   query.exec("insert into chaps (name, age) values "
              "('Bob', 20), ('Rob', 30), ('Sue', 25), ('Hob', 40);");
   QSqlTableModel model(nullptr, db);
   model.setTable("chaps");

   RowSelectionEmulatorProxy proxy;
   proxy.setSourceModel(&model);
   proxy.setSelectedBrush(QBrush(Qt::yellow));

   QTableView view;
   view.setModel(&proxy);
   view.setEditTriggers(QAbstractItemView::NoEditTriggers);
   view.setSelectionMode(QAbstractItemView::NoSelection);
   view.setSortingEnabled(true);
   QObject::connect(&view, &QAbstractItemView::clicked, [&proxy](const QModelIndex & index){
      proxy.toggleRowSelection(index);
   });

   QPushButton clearSelection("Clear Selection");
   QObject::connect(&clearSelection, &QPushButton::clicked, [&proxy]{ proxy.clearSelection(); });

   layout.addWidget(&view);
   layout.addWidget(&clearSelection);
   w.show();
   app.exec();
}

#include "main.moc"
于 2012-06-13T18:53:20.853 に答える