- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
我正在使用 QSqlTableModel
和 QTableView
来查看 SQLite 数据库表。
我希望表每隔一秒左右自动刷新一次(它不会是一个非常大的表 - 几百行)。我可以这样做——就像这样:
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*
}
但这会删除我所有的选择,因此选择最多只能持续一秒钟。这很烦人,因为 GUI 中的另一个 Pane 取决于选择的内容。如果未选择任何内容,则会重置为说明启动页面。
然后我尝试了一种有点 hacky 的方法,它获取选定的行号,重置表,然后选择该行。但这也不起作用,因为所选行可以根据添加到表格中的内容向上或向下移动。
我知道其他类有一个 dataChanged()
信号,这将是理想的。
你们有谁知道我如何让表刷新以反射(reflect)对数据库的更改(来自命令行使用或程序的其他实例)并保留当前选择?
我知道我可以从当前选择中获取数据,然后在重置后搜索同一行,然后重新选择它,但这似乎是一个适得其反的问题解决方案。
编辑:当前的解决方案尝试:
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);
}
}
}
好的,现在这或多或少可以工作了......
最佳答案
真正的交易是查询结果的主键。 Qt 的 API 提供了从 QSqlTableModel::primaryKey()
到列列表的相当迂回的路径。 primaryKey()
的结果是一个QSqlRecord
,你可以遍历它的field()s
看看它们是什么。您还可以从 QSqlTableModel::record()
中查找构成查询的所有字段。您可以在后者中找到前者以获取构成查询的模型列的列表。
如果您的查询不包含主键,则您必须自己设计一个并使用某种协议(protocol)提供它。例如,您可以选择如果 primaryKey().isEmpty()
为 true,则将模型返回的最后一列用作主键。由您决定如何键入任意查询的结果。
然后可以简单地通过它们的主键(组成键的单元格的值列表 - QVariantList
)对选定的行进行索引。为此,您可以使用 自定义选择模型 (QItemSelectionModel
),如果其设计没有损坏的话。 isRowSelected()
等关键方法不是虚拟的,您无法重新实现它们:(.
相反,您可以通过为数据提供自定义 Qt::BackgroundRole
来使用模拟选择的代理模型。您的模型位于表模型的顶部,并保留了选定键的排序列表。每次调用代理模型的 data()
时,您都会从底层查询模型中获取行的键,然后在排序列表中搜索它。最后,如果该项目被选中,您将返回一个自定义背景角色。您必须为 QVariantList
编写相关的比较运算符。如果 QItemSelectionModel
可用于此目的,您可以将此功能放入 isRowSelected()
的重新实现中。
该模型是通用的,因为您订阅了从查询模型中提取键的特定协议(protocol):即使用 primaryKey()
。
除了显式使用主键之外,您还可以使用持久索引(如果模型支持)。唉,至少在 Qt 5.3.2 之前,QSqlTableModel
在重新运行查询时不会保留持久索引。因此,一旦 View 更改排序顺序,持久索引就会失效。
下面是一个完整的示例,说明如何实现这样的野兽:
#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"
关于c++ - 如何在保留选择的同时刷新 QSqlTableModel?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11019694/
子类化 QSQLTableModel 并重写数据方法: class SclDataModel(QSqlTableModel): def __init__(self, parent=None):
我正在尝试继承 QSqlTableModel 以使我的表格中的数据以我需要的方式显示。 class TableViewModel(QSqlTableModel): def __init__(s
如果一条记录已被另一个数据库客户端插入到 mysql 表中,我如何收到通知,这可能吗? 最佳答案 void QSqlTableModel::beforeInsert(QSqlRecord &recor
我正在尝试添加始终为空的最后一行。现在我有代理模型,我在其上调用 insertRow() - 插入行。但是当更新最后一行(插入数据)时,我需要添加另一行。我应该连接到哪个信号? 有时会出现奇怪的错误
我正在尝试在 qt c++ 中访问 mysql 数据库。 我有不同的表格,想将它们输出到 QTableView 上。 我使用 QSqlTableModel,效果很好,但是当我想过滤结果时,我遇到了问题
我正在尝试从 ODBC 源 (informix db) 加载和显示数据。我可以创建并打开连接。但是我在使用 QSqlTableModel 时遇到了一些问题。下面的代码编译和运行没有错误,但不显示任何数
当一行要被编辑时,一个索引被传递到编辑对话框中。 编辑工作正常。 当我想添加一行时,我没有将索引传递给对话构造函数,因此它知道它应该添加一个新行。 这是对对话按钮使用react的函数代码 void D
阅读QSqlTableModel documentation我看到两种获取模型的方法: QSqlTableModel *model = new QSqlTableModel(parentObject,
如果我这样做: QSqlQuery searchQuery(QString("select * from people where id = %1").arg(1)); if (searchQuery
我有派生自 QSqlRelationalTableModel 的类。我使用 SQLite 数据库。我需要更改数据库文件。我关闭旧的数据库文件,调用 SetDatabaseName("path to n
我正在使用 QSqlTableModel 和 QTableView 来查看 SQLite 数据库表。 我希望表每隔一秒左右自动刷新一次(它不会是一个非常大的表 - 几百行)。我可以这样做——就像这样:
我将 QTableView 与 QSqlTableModel 一起使用。在我看来,我不显示包含记录 ID 的列。如果未显示在任何列中,如何获取所选行的 id? 感谢帮助 :) 最佳答案 你也可以直接从
我正在尝试将一个信号分配给一个按钮,以便它调用一个过滤并在 tableView 上显示特定数据的函数。 但是当我点击按钮时它说: Type error: setFilter(self, str) to
我有一个QTableView和QSqlTableModel。当我更新QTableView中的单元格时,我正在调用“ submitall”函数来更新数据库。 “ Submitall”已激活,但数据库中没
我的同事发现了 QSqlTableModel 的下一个问题: #define VARIANT_1 class SomeClass : public QWidget { Q_OBJECT pub
我看过很多讨论类似问题的教程/解释,但我不知道如何将其应用于这种情况。 我是一名 Qt/C++ 新手,正在尝试构建连接到一个简单的 GUI 应用程序SQLite 数据库。我有 UI 表单,它是使用 Q
我有一个包含重复记录的表。 我使用以下代码将数据放入 QTableView 中: QSqlTableModel *dataModel = new QSqlTableModel(); dataModel
我有一个连接到 MySQL 数据库的 PyQt5 应用程序。我制作了连接到同一模型的两个 View 。 View 从数据库中提取数据就好了。我对一个字段中的任何字段所做的任何更改都会反射(reflec
我有一个 tableView 示例: 比如当数据列状态为S时。表格的背景颜色为绿色,when N 为红色。 我想要的例子: 我的代码: MainWindow::MainWindow(QWidget *
我正在尝试更新一条记录,我有这个: tableModel->select(); QModelIndex index = ui.tableView->currentIndex(); QString sq
我是一名优秀的程序员,十分优秀!