我将QLineEdit作为搜索输入。
当搜索输入的值更改时,它会调用广告位:
void
myWidget::slotApplyItemsFilter(const QString &searchString)
{
viewArea.applyItemsFilter(searchString.trimmed());
}
这是applyItemsFilter方法的实现:
void
myViewArea::applyItemsFilter(const QString &searchString)
{
for (int i = 0; i < model.rowCount(QModelIndex()); i += 1) {
setRowHidden(
i,
searchString.isEmpty()
? false
: !model.isMatched(i, searchString)
);
}
}
在这里实现模型的isMatched方法:
bool
myModel::isMatched(
const int row,
const QString &searchString
) const
{
return (
(0 > row && items.size() <= row)
? false
: items.at(row).name.contains(searchString, Qt::CaseInsensitive)
);
}
一切正常。但是,当视图/模型包含许多项目(例如1000)时,它会缓慢运行并冻结QLineEdit(无法键入符号,不,我可以,但是它看起来像冻结的队列),而不会为每个项目计算。
UPD:是的,我尝试为插槽设置Qt :: QueuedConnection,这无济于事。
如何进行不冻结的搜索输入?
答案 0 :(得分:1)
您不能仅“解冻”输入窗口小部件。输入被冻结是因为您很长时间没有将控制权返回到事件循环,因此整个GUI被冻结了。
排队的连接无济于事,因为您要批量运行所有代码。
一项基本改进是在更改行可见性时禁用视图小部件更新。
最简单的方法是使用过滤代理模型。
class myViewArea : .... {
QSortFilterProxyModel viewModel;
...
};
myViewArea::myViewArea(...) {
...
viewModel.setSourceModel(&model);
viewModel.setFilterKeyColumn(...); // the column holding the name
viewModel.setFilterCaseSensitivity(false);
setModel(&viewModel);
}
void myViewArea::applyItemsFilter_viewModel(const QString &needle) {
// allows all when needle is empty
NoUpdates noUpdates(this);
viewModel.setFilterFixedString(needle);
}
class NoUpdates {
Q_DISABLE_COPY(NoUpdates)
QWidget *const w;
bool const prev = w->updatesEnabled();
public:
NoUpdates(QWidget *w) : w(w) { w->setUpdatesEnabled(false); }
~NoUpdates() { w->setUpdatesEnabled(prev); }
};
或者,您需要使代码不会长时间阻塞事件循环。一种方法是与主线程协同运行搜索。最好在最小化重绘成本的方向上迭代索引,即始终从模型的部分开始,该部分由工作最多的行所描绘,具有最多的项目。
void myViewArea::applyItemsFilter_gui(const QString &needle)
{
bool inFirstHalf = rowAt(0) <= model.rowCount()/2;
// iterate backwards in the first half of the rows
int dir = inFirstHalf ? -1 : +1;
int i = dir > 0 ? model.rowCount()-1 : 0;
auto isValidRow = [this, dir](int i){
return (dir > 0 && i < model.rowCount()) || i >= 0;
};
runCooperatively(this, [this, needle, isValidRow, i, dir,
n = NoUpdates(this)]() mutable
{
if isValidRow (i) do {
setRowVisible(i, needle.isEmpty() || model.isMatched(i, needle), this);
i += dir;
} while isRowInViewport(i); // update all visible rows in one go
return isValidRow(i);
});
}
bool myViewArea::isRowInViewport(int row) const {
auto first = indexAt(viewport()->rect().topLeft());
auto last = indexAt(viewport()->rect().bottomRight());
return row >= 0 && row < model.rowCount()
&& row >= first.row() && (!last.isValid() || row <= last.row());
}
另一种方法是同时运行不需要在主线程上的代码。名称收集在一个列表对象中,并传递给计算可见性的并发代码。计算可见性后,将在主线程中协同设置行可见性。
void myViewArea::applyItemsFilter_concurrent(const QString &needle)
{
auto visible = QtConcurrent::mapped(getNames(),
[needle](const QString &name){ return isMatched(needle, name); });
runCooperativelyAfter(this, future, [this, visible, i = 0,
n = NoUpdates(this)]() mutable
{
if (i >= rowCount() || i >= visible.resultCount()) return false;
setRowVisible(i, visible.resultAt[i], this);
return ++i;
});
}
前面两种方法在代码外观上非常相似,并以连续传递样式编写。当然,使用协程会更好-这就是TODO。
可以以最通用的方式协同运行代码,如下所示:
/// Runs a functor cooperatively with the event loop in the context object's
/// thread, as long as the functor returns true. The functor will run at least
/// once, unless the context object gets destroyed before control returns to
/// event loop.
template <class Fun>
static void runCooperatively(QObject *context, Fun &&fun) {
QMetaObject::invokeMethod(context, [context, f = std::forward<Fun>(fun)]{
auto *hook = new QTimer(context);
QObject::connect(hook, &QTimer::timeout, [hook, fun = std::forward<Fun>(f)]{
if (!fun()) hook->deleteLater();
});
hook->start();
});
}
/// Runs a functor cooperatively after a future is completed
template <class Fun, typename Res>
static void runCooperativelyAfter(QObject *ctx, const QFuture<Res> &future, Fun &&fun) {
auto *watcher = new QFutureWatcher<Res>(ctx);
watcher->setFuture(future);
QObject::connect(watcher, &QFutureWatcher::finished,
[future, ctx, f = std::forward<Fun>(fun) {
future->deleteLater();
runCooperatively(ctx, [fun = std::forward<Fun>(f)]{ fun(); });
}
);
}
其他共享功能如下:
// Note: an empty needle would match per QString search semantics,
// but it's unexpected - better to make it explicit so that
// a maintainer doesn't have to dig in the documentation.
// ***Static Method***
bool myModel::isMatched(const QString &needle, const QString &name)
{
assert(!needle.isEmpty());
return name.contains(needle, Qt::CaseInsensitive);
}
bool myModel::isMatched(const int row, const QString &needle) const
{
return row >= 0 && row < items.size() &&
isMatched(needle, items.at(row).name);
}
QStringList myModel::getNames() const
{
// The below should be fast enough, but let's time it to make sure
return time([this]{
QStringList names;
names.reserve(items.size());
for (auto &item : items) names.push_back(item.name);
return names;
});
}
template <class C> static void setRowVisible(int row, bool vis, C *obj) {
obj->setRowHidden(row, !vis);
}
template <class Fun>
static typename std::result_of<Fun()>::type time(const Fun &code) {
struct Timing {
QElapsedTimer timer;
Timing () { timer.start(); }
~Timing () { qDebug() << timer.elapsed(); }
} timing;
return code();
}
您的原始代码具有很多双重反转的逻辑,这使得人们很难理解正在发生的事情。是的,三元运算符的发现可以使我感到头晕,我明白:)然而,这种复杂性是无缘无故的,因为C ++具有短路评估功能,并且三元运算符的体操是不必要的。
答案 1 :(得分:0)
另一种方法是在进行长时间的处理操作时手动调用事件循环。这可以通过在搜索循环中使用QCoreApplication::processEvents(..)来实现,但是只能如此频繁地进行,否则会浪费更多的时钟周期。
paintComponent(Graphics g)