I have a performance issue while displaying large amounts MapItems on a Map provided by the QML Location module. I already asked the question here (https://forum.qt.io/topic/79229/large-amount-of-qml-mapitems), but nobody could help me, so I wanted to try it here once. I also found this question (How to use the QML/QtLocation module for displaying a large amount of offline data on a map?), but before adding another dependency, I wanted to see if my code can be improved so that QML can handle this situation without any help.
I am currently trying to plot a large amount of items onto a QML Map (30,000 - 120,000 points). These items shall be updated dependent of the position of a QSlider. Performance decreases strongly from about 1,000 items upwards, when I use 30,000 it takes several minutes until the QML Map has all the data visualized and is responsive again. I have a machine which is absolutely capable of fulfilling this task in general, so I think the problem is QML. I am using Qt 5.8.
Is there any way to improve this performance or is it just not possible with a QML-map to plot so many MapItems at a time? I tried MapCircles, Polylines, Polygons and MapQuickItems with images, but for me it seems like the performance issue just arises from adding this amount of MapItems, as I could not see a significant difference in processing time between these types.
I have more data on the map visualized, which should not be refreshed every time the QSlider is moved. Even though I tried just to clear all MapItems and add the new ones for performance tests, but even this did not improve the performance.
My code (a bit abstracted) looks like this:
///-------------- Widget.cpp-----------------///
void ProcessInput(int qslider_pos) {
QVariantList lat_vec;
QVariantList lon_vec;
// Fill vectors with lateral and longitudinal positions
// ...
// Clean current points on map and draw new ones
SendToQmlFuncRemovePoints();
SendToQmlFuncAddPoints(lat_vec, lon_vec);
}
void QmlConnector::SendToQmlFuncRemovePoints()
{
QVariant returnedValue;
QMetaObject::invokeMethod(QmlMapSingleton::instance()->GetRoot(), "remove_points",
Q_RETURN_ARG(QVariant, returnedValue));
}
void QmlConnector::SendToQmlFuncAddPoints(QVariantList input_one, QVariantList input_two)
{
QVariant returnedValue;
QMetaObject::invokeMethod(QmlMapSingleton::instance()->GetRoot(), "add_points",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, QVariant::fromValue(input_one)), Q_ARG(QVariant, QVariant::fromValue(input_two)));
}
.
///-------------- Map.qml -----------------///
Map {
anchors.fill: parent
property variant points: ({})
property int pointCounter: 0
Plugin
{
id: osmplugin
name: "osm"
PluginParameter { name: "osm.mapping.highdpi_tiles"; value: true }
}
Component.onCompleted: {
points = new Array();
}
id: map
plugin: osmplugin
//Javascript functions
function add_points(array_lat, array_lon) {
var myArray = new Array()
var component = Qt.createComponent("mapcircle.qml");
for (var i=0; i<array_lat.length; i++)
{
var object = component.createObject(map, { "center": QtPositioning.coordinate(array_lat[i], array_lon[i]})
map.addMapItem(object)
myArray.push(object)
}
map.points = myArray
}
function remove_points() {
var count = map.points.length
for (var i = 0; i<count; i++){
map.removeMapItem(map.points[i])
map.points[i].destroy()
}
map.points = []
}
}
.
///-------------- mapcircle.qml -----------------///
import QtQuick 2.0
import QtLocation 5.6
MapCircle {
radius: 1
border.width: 0
color: 'green'
}
Qt says that the performance decreases with the number of elements added to the map. Do you need all the points to be visible on the map in the same time, if not you can play around with visibility.
Can't you use QQuickPaintedItem to paint the points in C++ and wrap it into an MapQuickItem, if you have multiple polygonsfor e.g? But also there are some limitation, you cannot have to big images displayed.
If you need all the points maybe you can have different points based on the map zoom level and reduce the number of points added to the map at small zoom level, as was recommended on the other group ...
Related
So, I have come across a solution for this problem using Get_storage thanks to a couple of great explanations about the topic. I managed to work with getx and the Provider package to save data when adding new stuff and reading it when starting the application (thats the behavior i'm going for here). Said that, i'm having a hard time trying to remove data from memory.
Context
The project is a to-do list app, the front end is working perfectly, but when it comes to storage it gets more complicated. The thing is that i'm very new to flutter and mobile development, i recieved some help but this kind of stuff is still foggy in my brain, i could not remove data using the same logic. When i called box.Remove('key') like the docs say, my ENTIRE list got removed. I don't have a single clue why that happaned.
And so i wonder if i could understand it more by reading through some more explanations, i know Shared Preferences is a great deal do work with this kind of situation, but i would also be confortable with a solution using get_storage since i'm more familiar with it.
the code:\
I'm calling these lists inside a listView on a different file with the help of Provider - -
List<Task> _tasks = [
Task(
name: "Title",
description: "Description",
),
];
Adding tasks to my ListView - -
void add(String newTitle, newDesc) {
final task = Task(name: newTitle, description: newDesc);
_tasks.add(task);
notifyListeners();
}
Here is the removal of a task from the ListView - -
void removeTasks(Task task) {
_tasks.remove(task);
notifyListeners();
}
I tried to implement a logic to write and read data, it worked. But i also tried to use this removeTasks method to remove from storage as well by calling box.Remove('tasks'); ('tasks' was the key passed to the writing and reading methods). It removed everything from memory since my listview got empty.
Since i'm not that experienced, i went through the documentation and could understand some of the SharedPreferences Explanation (same with got_storage) but i'm having a hard time when trying to apply it to my code.
I would appreciate any help using get_storage OR shared preferences to this problem.
Where i'm calling the deletion:
// bool variables that control the state of the screen
// since i can change it to show done tasks or on goind tasks
// dont mind that, i think its irrelevant to the problem.
//
bool isActiveDoing = true;
bool isActiveDone = false;
List finalArray = []; //it will store the tasks
class TaskList extends StatefulWidget {
#override
_TaskListState createState() => _TaskListState();
}
class _TaskListState extends State<TaskList> {
#override
Widget build(BuildContext context) {
//dont mind the if else as well, its not part of the problem
//just using it to handle the state of the screen
if (isActiveDoing) {
finalArray = Provider.of<TasksFunctions>(context).tasks;
}
//TasksFunctions is a class with methods on regards to the storage
//it contains add tasks, remove, etc... i'm using provider to
//link those to the screens with the notifyListeners
if (isActiveDone) {
finalArray = Provider.of<TasksFunctions>(context).doneTasks;
}
//now here is where i call the class tha has the deletion method
return Consumer<TasksFunctions>(
builder: (context, tasksFunctions, child) {
return ListView.builder(
//list view tha has all the tasks
itemCount: finalArray.length,
itemBuilder: (context, index) {
final task = finalArray[index];
//using the slidableWidget to wrap the deletion method
return SlidableWidget(
onDismissed: (action) {
if (isActiveDoing) {
Provider.of<TasksFunctions>(context, listen: false)
.removeTask(task);
//so here is where i'm deleting those tasks, calling that method
//listed up on this post
}
if (isActiveDone {
Provider.of<TasksFunctions>(context, listen: false)
.removeDone(task);
}
},
);
},
);
},
);
}
}
So i spent some time translating the code, but i think that it does not match any of flutter's good practices principles.
I also tried calling storageList.remove(task); and then rewriting it with the box.write('tasks', storageList); but nothing was removed from the memory (maybe because i didn't loop through the whole storageLists searching for the right index i guess)
Sounds like your code is based on my answer to your original question about this.
If that's the case, then the key tasks is the key to the entire list of maps, not a single map or Task. So it's behaving as expected when it wipes all of your tasks.
In order to persist any changes, you'd have to remove that particular map (Task) from storageList then overwrite the box again with box.write('tasks', storageList); It will save over the same list of maps and persist any deletions you made.
If you share your code where you're trying to delete the task and whats going on around it I can give you a more specific example.
EDIT: Answering question in comments.
If you wanted to go the UniqueKey route you wouldn't need the index at all. You could do something like this.
class Task {
final String name;
final String description;
final String key; // not an actual Key but will take the String value of a UniqueKey
Task({this.key, this.name, this.description});
}
When you add a new Task it would look like this.
final task = Task(
description: 'test description',
name: 'test name',
key: UniqueKey().toString());
Then you could use a Map of maps instead of a list of maps and use the key for both.
Map storageList = {};
void addAndStoreTask(Task task) {
_tasks.add(task);
final Map storageMap = {}; // temporary map that gets added to storage
storageMap['name'] = task.name;
storageMap['description'] = task.description;
storageMap['key'] = task.key;
storageList[task.key] = storageMap; // adding temp map to storageList
box.write('tasks', storageList); // adding map of maps to storage
}
Then your restore function would look like this:
void restoreTasks() {
storageList = box.read('tasks'); // initializing list from storage
storageList.forEach((key, value) { // value here is each individual map that was stored
final task =
Task(name: value['name'], description: value['description'], key: key);
_tasks.add(task);
});
}
Then when you go to delete, you iterate through the list and find the matching key.
I'm trying to make small platformer game, just in learning purposes. I've tried to animate character with array of SKTextures like this:
let animation: SKAction = SKAction.animate(
with: frames.map {
let texture : SKTexture = SKTexture(imageNamed: $0)
texture.filteringMode = SKTextureFilteringMode.nearest
return texture
},
timePerFrame: 0.15
)
frames variable is array of SKTextures. I've used map to set filtering mode for each texture. This works fine and dandy, but than i've discovered that i can create animations (actions) in ActionEditor in XCode with AnimateWithTextures action. And here lies my problem. This is much more efficient, but i can't change filtering mode for textures in animation in ActionEditor. I've looked into SKTexture class and saw this:
open class SKTexture : NSObject, NSCopying, NSCoding {
...
/**
The filtering mode the texture should use when not drawn at native size. Defaults to SKTextureFilteringLinear.
*/
open var filteringMode: SKTextureFilteringMode
...
}
If i'm not mistaken open means that i can override variable, but i don't know how. I want to set default value of filteringMode to SKTextureFilteringMode.nearest for all SKTextures so ActionEditor will use textures with texture filtering set to nearest neighbor.
Also if you know how to set filtering mode for textures in specific action – i would like to know how to do it too. Thanks
you can create a function to setup the animation for you, and create an extension to SKTexture with your custom init, and when your ready just call it to your node.
extension SKTexture { // Declare this extension outside your class decleration
convenience init(customImageNamed: String) {
self.init(imageNamed: customImageNamed)
self.filteringMode = .nearest
}
}
func setupAnimationWithPrefix(_ prefix: String, start: Int, end: Int, timePerFrame: TimeInterval) -> SKAction{
var textures = [SKTexture]()
for i in start...end{
textures.append(SKTexture(customImageNamed: "\(prefix)\(i)"))
}
return SKAction.animate(with: textures, timePerFrame: timePerFrame)
}
if you have 10 images named image1, image2, image3 ect..then this is how you would use this function
func runAction(){
let action = setupAnimationWithPrefix("image", start: 1, end: 10, timePerFrame: 0.1)
yourNode.run(action)
} // you can change timePerFrame to your liking i find 0.1 sec per frame the best
Unfortunately you can't override a variable without subclassing it, that is to do something like
class MyTexture: SKTexture {
from there, you could override the variable:
override var filteringMode: SKTextureFilteringMode {
get {
return .nearest
}
set {
}
}
That should work, but now you can't change the filtering mode, ever (without going through a bunch of hooplah).
So the more apt way would be to set the default value via the initializer, but SKTexture has no public designated initializers, so that option is out of the window.
So your best bet is going to be the convenience init extension by Arie, or just make a plain function that returns a new texture:
func nearestTexture(imageNamed name: String) -> SKTexture {
let newTex = SKTexture(imageNamed: name)
newTex.filteringMode = .nearest
return newTex
}
let texture: SKTexture = nearestTexture(imageNamed: "lol")
ActionEditor is still very limited in features, you are going to have to write some kind of code behind to allow for texture filtering. If you do not plan on scaling your sprites (Note I said sprite, not scene, these are two different behaviors), then I would not even worry about texture filtering, the time difference would be negligible.
How i should add and remove series from qchart?
The main mess is that QChart some how store QAxis and QSeries, QSeries themselves refer to QAxis.Above that all that objects are heavy connected.
So instead of pseudo code like this:
void someclass::set_series(QLineSeries*someQLineSeries){
somechart->removeAllSeries();
somechart->removeAllAxis();
somechart->addSeries(someQLineSeries);
}
I should use that:
(zlineseries additionaly store 2 pointers to its axis)
void zhplotview::attach_dt_series(ZLineSeries *series)
if(chart)
{
qDebug()<<"NOT DELETED CHART";
if(viewer)
{
qDebug()<<"NOT DELETED VIEWER";
qDebug()<<"Okay. Try to clear old links and create chart again";
//let chart have 1 series
auto slist=chart->series();
ZLineSeries *old_series=dynamic_cast<ZLineSeries*>(slist.at(0));
old_series->detachAxis(old_series->axis_x);
old_series->detachAxis(old_series->axis_y);
chart->removeAxis(old_series->axis_x);
chart->removeAxis(old_series->axis_y);
vbl->removeWidget(viewer);
chart->removeSeries(old_series);
disconnect(viewer);
disconnect(chart);
delete chart;
delete viewer;
}
else
{
qDebug()<<"chart is 0, but viewer not";
return;
}
}
chart=new QChart();
chart->addSeries(series);
chart->addAxis(series->axis_x,Qt::AlignBottom);
chart->addAxis(series->axis_y,Qt::AlignLeft);
series->attachAxis(series->axis_x);
series->attachAxis(series->axis_y);
viewer=new QZChartView(chart,this);
vbl->addWidget(viewer);
I know it's three years later, but I use a little workaround for this, maybe it's interesting for someone.
On every delta I generate a new chart, and just replace the old one. In order to be fast enough, I keep some data pre-calculated in stack.
I'm using a QListView with a custom model derived from QAbstractItemModel. I have on the order of millions of items. I have called listView->setUniformItemSizes(true) to prevent a bunch of layout logic from being called when I'm adding items to the model. So far, everything works as expected.
The problem is that using the keyboard to navigate the list is slow. If I select an item in the list, then press up/down, the selection moves fast until the selection needs to scroll the list. Then it becomes extremely laggy. Pressing page-up or page-down is also very laggy. The problem seems to be when an item is selected (aka the "current item") with the keyboard and the list is also scrolled up/down.
If I use the mouse, navigating the list is fast. I can use the mouse wheel, which is fast. I can drag the scroll bar up/down as fast as I want--from the top of the list to the bottom--and the list view updates wickedly fast.
Any ideas on why the combination of changing selections and scrolling the list is so slow? Is there a viable work-around?
Update 9/9/15
In order to better illustrate the issue, I'm providing amplifying information in this update.
Performance Issues with KEYBOARD + SCROLLING
This is mostly a performance question, although it does tie in with the user experience (UX) somewhat. Check out what happens as I use the keyboard to scroll through a QListView:
Notice the slow-down near the bottom? This is the focal point of my question. Let me explain how I am navigating the list.
Explanation:
Starting at the top, the first item in the list is selected.
Pressing and holding the down arrow key, the current item (selection) is changed to the next item.
Changing selection is fast for all of the items that are currently in view.
As soon as the list needs to bring the next item into view, the selection rate slows down significantly.
I expect that the list should be able to scroll as fast as the typematic rate of my keyboard--in other words, the time it takes to select the next item should not slow down when the list is scrolled.
Fast Scrolling with MOUSE
Here's what it looks like when I use the mouse:
Explanation:
Using the mouse, I select the scroll bar handle.
Quickly dragging the scroll bar handle up and down, the list is scrolled accordingly.
All movements are extremely fast.
Note that no selections are being made.
This proves two main points:
The model is not the problem. As you can see, the model has no problem whatsoever performance-wise. It can deliver the elements faster than they can be displayed.
Performance is degraded when selecting AND scrolling. The "perfect storm" of selecting and scrolling (as illustrated by using the keyboard to navigate through the list) causes the slowdown. As a result, I surmise that Qt is somehow doing a lot of processing when selections are being made during scrolling that aren't normally performed.
Non-Qt Implementation is FAST
I want to point out that my issue seems to be specific to Qt.
I have already implemented this type of thing before using a different framework. What I am trying to do is within the scope of model-view theory. I can do exactly what I am describing at blazing fast speeds using juce::ListBoxModel with a juce::ListBox. It's stupid fast (plus, there's no need to create a duplicate index such as a QModelIndex for every single item when each item already has a unique index). I get that Qt needs a QModelIndex for each item for its model-view architecture, and although I don't like the overhead cost, I think I get the rational and I can live with it. Either way, I don't suspect that these QModelIndexes are what is causing my performance slow-down.
With a JUCE implementation, I can even use the page-up & page-down keys to navigate the list, and it just blazes through the list. Using the Qt QListView implementation, it chugs along and is laggy, even with a release build.
A model-view implementation using the JUCE framework is extremely fast. Why is the Qt QListView implementation such a dog?!
Motivating Example
Is it hard to imagine why you'd need so many items in a list view? Well, we've all seen this kind of thing before:
This is the Visual Studio Help Viewer index. Now, I haven't counted all of the items--but I think we'd agree that there are a lot of them! Of course to make this list "useful," they added a filter box that narrows down what is in the list view according to an input string. There aren't any tricks here. It's all practical, real-world stuff we've all seen for decades in desktop applications.
But are there millions of items? I'm not sure it matters. Even if there were "only" 150k items (which is roughly accurate based on some crude measurements), it's easy to point out that you have to do something to make it useable--which is what a filter will do for you.
My specific example uses a list of German words as a plain text file with slightly more than 1.7 million entries (including inflected forms). This is probably only a partial (but still significant) sample of words from the German text corpus that was used to assemble this list. For linguistic study, this is a reasonable use case.
Concerns about improving the UX (user experience) or filtering are great design goals, but they are out of the scope of this question (I'll certainly address them later in the project).
Code
Want a code example? You got it! I'm not sure how useful it will be; it's as vanilla as it gets (about 75% boilerplate), but I suppose it will provide some context. I realize that I'm using a QStringList and that there is a QStringListModel for this, but the QStringList that I'm using to hold the data is a placeholder--the model will eventually be somewhat more complicated, so in the end, I need to use a custom model derived from QAbstractItemModel.
//
// wordlistmodel.h ///////////////////////////////////////
//
class WordListModel : public QAbstractItemModel
{
Q_OBJECT
public:
WordListModel(QObject* parent = 0);
virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex& index) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex & parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
public slots:
void loadWords();
signals:
void wordAdded();
private:
// TODO: this is a temp backing store for the data
QStringList wordList;
};
//
// wordlistmodel.cpp ///////////////////////////////////////
//
WordListModel::WordListModel(QObject* parent) :
QAbstractItemModel(parent)
{
wordList.reserve(1605572 + 50); // testing purposes only!
}
void WordListModel::loadWords()
{
// load items from file or database
// Due to taking Kuba Ober's advice to call setUniformItemSizes(true),
// loading is fast. I'm not using a background thread to do
// loading because I was trying to visually benchmark loading speed.
// Besides, I am going to use a completely different method using
// an in-memory file or a database, so optimizing this loading by
// putting it in a background thread would obfuscate things.
// Loading isn't a problem or the point of my question; it takes
// less than a second to load all 1.6 million items.
QFile file("german.dic");
if (!file.exists() || !file.open(QIODevice::ReadOnly))
{
QMessageBox::critical(
0,
QString("File error"),
"Unable to open " + file.fileName() + ". Make sure it can be located in " +
QDir::currentPath()
);
}
else
{
QTextStream stream(&file);
int numRowsBefore = wordList.size();
int row = 0;
while (!stream.atEnd())
{
// This works for testing, but it's not optimal.
// My real solution will use a completely different
// backing store (memory mapped file or database),
// so I'm not going to put the gory details here.
wordList.append(stream.readLine());
++row;
if (row % 10000 == 0)
{
// visual benchmark to see how fast items
// can be loaded. Don't do this in real code;
// this is a hack. I know.
emit wordAdded();
QApplication::processEvents();
}
}
if (row > 0)
{
// update final word count
emit wordAdded();
QApplication::processEvents();
// It's dumb that I need to know how many items I
// am adding *before* calling beginInsertRows().
// So my begin/end block is empty because I don't know
// in advance how many items I have, and I don't want
// to pre-process the list just to count the number
// of items. But, this gets the job done.
beginInsertRows(QModelIndex(), numRowsBefore, numRowsBefore + row - 1);
endInsertRows();
}
}
}
QModelIndex WordListModel::index(int row, int column, const QModelIndex& parent) const
{
if (row < 0 || column < 0)
return QModelIndex();
else
return createIndex(row, column);
}
QModelIndex WordListModel::parent(const QModelIndex& index) const
{
return QModelIndex(); // this is used as the parent index
}
int WordListModel::rowCount(const QModelIndex& parent) const
{
return wordList.size();
}
int WordListModel::columnCount(const QModelIndex& parent) const
{
return 1; // it's a list
}
QVariant WordListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
else if (role == Qt::DisplayRole)
{
return wordList.at(index.row());
}
else
{
return QVariant();
}
}
//
// mainwindow.h ///////////////////////////////////////
//
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void updateWordCount();
private:
Ui::MainWindow *ui;
WordListModel* wordListModel;
};
//
// mainwindow.cpp ///////////////////////////////////////
//
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->listView->setModel(wordListModel = new WordListModel(this));
// this saves TONS of time during loading,
// but selecting/scrolling performance wasn't improved
ui->listView->setUniformItemSizes(true);
// these didn't help selecting/scrolling performance...
//ui->listView->setLayoutMode(QListView::Batched);
//ui->listView->setBatchSize(100);
connect(
ui->pushButtonLoadWords,
SIGNAL(clicked(bool)),
wordListModel,
SLOT(loadWords())
);
connect(
wordListModel,
SIGNAL(wordAdded()),
this,
SLOT(updateWordCount())
);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateWordCount()
{
QString wordCount;
wordCount.setNum(wordListModel->rowCount());
ui->labelNumWordsLoaded->setText(wordCount);
}
As noted, I've already reviewed and taken Kuba Ober's advice:
QListView takes too long to update when given 100k items
My question is not a duplicate of that question! In the other question, the OP was asking about loading speed, which as I've noted in my code above, is not a problem due to the call to setUniformItemSizes(true).
Summary Questions
Why is navigating a QListView (with millions of items in the model) using the keyboard so slow when the list is scrolled?
Why does the combination of selecting and scrolling items cause a slow-down?
Are there any implementation details that I am missing, or have I reached a performance threshold for QListView?
1. Why is navigating a QListView (with millions of items in the model)
using the keyboard so slow when the list is scrolled?
Because when you navigate through your list using the keyboard, you enter the internal Qt function QListModeViewBase::perItemScrollToValue, see stack:
Qt5Widgetsd.dll!QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize, QAbstractItemView::ScrollHint hint, Qt::Orientation orientation, bool wrap, int itemExtent) Ligne 2623 C++
Qt5Widgetsd.dll!QListModeViewBase::verticalScrollToValue(int index, QAbstractItemView::ScrollHint hint, bool above, bool below, const QRect & area, const QRect & rect) Ligne 2205 C++
Qt5Widgetsd.dll!QListViewPrivate::verticalScrollToValue(const QModelIndex & index, const QRect & rect, QAbstractItemView::ScrollHint hint) Ligne 603 C++
Qt5Widgetsd.dll!QListView::scrollTo(const QModelIndex & index, QAbstractItemView::ScrollHint hint) Ligne 575 C++
Qt5Widgetsd.dll!QAbstractItemView::currentChanged(const QModelIndex & current, const QModelIndex & previous) Ligne 3574 C++
Qt5Widgetsd.dll!QListView::currentChanged(const QModelIndex & current, const QModelIndex & previous) Ligne 3234 C++
Qt5Widgetsd.dll!QAbstractItemView::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) Ligne 414 C++
Qt5Cored.dll!QMetaObject::activate(QObject * sender, int signalOffset, int local_signal_index, void * * argv) Ligne 3732 C++
Qt5Cored.dll!QMetaObject::activate(QObject * sender, const QMetaObject * m, int local_signal_index, void * * argv) Ligne 3596 C++
Qt5Cored.dll!QItemSelectionModel::currentChanged(const QModelIndex & _t1, const QModelIndex & _t2) Ligne 489 C++
Qt5Cored.dll!QItemSelectionModel::setCurrentIndex(const QModelIndex & index, QFlags<enum QItemSelectionModel::SelectionFlag> command) Ligne 1373 C++
And this function does:
itemExtent += spacing();
QVector<int> visibleFlowPositions;
visibleFlowPositions.reserve(flowPositions.count() - 1);
for (int i = 0; i < flowPositions.count() - 1; i++) { // flowPositions count is +1 larger than actual row count
if (!isHidden(i))
visibleFlowPositions.append(flowPositions.at(i));
}
Where flowPositions contains as many items as your QListView, so this basically iterates through all your items, and this will definitely take a while to process.
2. Why does the combination of selecting and scrolling items cause a slow-down?
Because "selecting and scrolling" makes Qt call QListView::scrollTo (to scroll the view to a specific item) and this is what ends up calling QListModeViewBase::perItemScrollToValue. When you scroll using the scroll bar, the system does not need to ask the view to scroll to a specific item.
3. Are there any implementation details that I am missing, or have I reached a performance threshold for QListView?
I'm afraid you are doing the things right. This is definitely a Qt bug. A bug report must be done to hope having this fixed in later releases. I submitted a Qt bug here.
As this code is internal (private data classes) and not conditionnal to any QListView setting, I see no way to fix it except by modifying and recompiling the Qt source code (but I don't know exactly how, this would require more investigation). The first function overidable in the stack is QListView::scrollTo but I doubt it would be easy to oevrride it without calling QListViewPrivate::verticalScrollToValue...
Note: The fact that this function goes through all items of the view was apparently introduced in Qt 4.8.3 when this bug was fixed (see changes). Basically, if you don't hide any items in your view, you could modify Qt code as below:
/*QVector<int> visibleFlowPositions;
visibleFlowPositions.reserve(flowPositions.count() - 1);
for (int i = 0; i < flowPositions.count() - 1; i++) { // flowPositions count is +1 larger than actual row count
if (!isHidden(i))
visibleFlowPositions.append(flowPositions.at(i));
}*/
QVector<int>& visibleFlowPositions = flowPositions;
Then you'll have to recompile Qt and I'm pretty sure this will fix the issue (not tested however). But then you'll see new problems if you one day hide some items...to support filtering for instance!
Most likely the right fix would have been to have the view maintain both flowPositions and visibleFlowPositions to avoid creating it on the fly...
I have made the following test:
First of all i create a class to check in the calls:
struct Test
{
static void NewCall( QString function, int row )
{
function += QString::number( row );
map[ function ]++;
}
static void Summary( )
{
qDebug() << "-----";
int total = 0;
QString data;
for( auto pair : map )
{
data = pair.first + ": " + QString::number( pair.second );
total += pair.second;
qDebug( ) << data;
}
data = "total: " + QString::number( total ) + " calls";
qDebug() << data;
map.clear();
}
static std::map< QString, int > map;
};
std::map<QString,int> Test::map;
Then I insert a call to NewCall in index, parent and data methods of WordListModel. Finally i add a QPushButton in the dialog, the clicked signal is linked to a method which call to Test::Summary.
The steps of the test are the next:
Select the last showed item of the list
Press the Summary button to clear the calling list
With tab key select the list view again
Perform a scroll with the direction keys
Press Summary button again
The printed list shows the problem. QListView widget makes a big number of calls. It seems the widget is reloading all the data from the model.
I don't know if it can be improved but you can't do anything but filter the list to limit the number of items to show.
Unfortunately, I believe that you can't do much about this.
We don't have much control over widgets.
Although you can avoid that issue by using ListView instead.
If you try my quick example below you'll notice how fast it can be even using delegates which is costly.
Here is the example:
Window{
visible: true
width: 200
height: 300
property int i: 0;
Timer {
interval: 5
repeat: true
running: true
onTriggered: {
i += 1
lv.positionViewAtIndex(i, ListView.Beginning)
}
}
ListView {
id:lv
anchors.fill: parent
model: 1605572
delegate: Row {
Text { text: index; width: 300; }
}
}
}
I put a Timer to simulate the scrolling, but of course you can turn on or off that timer depending on whether keys are pressed as well as changing i += 1 by i += -1 if ▲ is pressed instead of ▼. You'd have to add overflow and underflow checks too.
You can also choose the scrolling speed by changing interval of Timer. Then it's just a matter of modifying the selected element's color etc. to show it's selected.
On top of which you can use cacheBuffer with ListView to cache more elements but I don't think it is necessary.
If you want to use QListView anyway take a look at this example: http://doc.qt.io/qt-5/qtwidgets-itemviews-fetchmore-example.html
Using the fetch method allow to keep performance even with big datasets. It allows you to fill the list as you scroll.
I have a TMS layer that looks something like this:
var v = 1;
my_tms = new OpenLayers.Layer.TMS(
"My TMS",
"my_mapserver.php?v="+my_var+"&",
{ transparent: 'true', type:'png', getURL:get_my_url }
);
Where my_mapserver.php returns map tiles according to the value of v.
The app allows users to change v, and I simply want to refresh the my_tms layer, however, so far the only way I can get it to refresh is by destroying the map and recreating it.
I thought I could just do something like this:
v = 2;
my_tms = new OpenLayers.Layer.TMS(
"My TMS",
"my_mapserver.php?v="+my_var+"&",
{ transparent: 'true', type:'png', getURL:get_my_url }
);
my_tms.redraw();
However, these tiles are not getting requested when I redraw().
Any help is appreciated.
As TMS layers inherits from Grid layer you could try to call clearGrid() method to remove all existing tiles and then spiralTileLoad() to load new ones.
layer.redraw();, OpenLayers.Strategy.Refresh and clearGrid() didn't help me in reloading tiles of OpenLayers.Layer.TMS layer in OpenLayers 2.13.1, but helped:
layer.mergeNewParams({});