Smoothly Updating QGraphicsscene - qt

Hi I have managed to add a number of qgraphicsitems to a qgraphicsscene using the code below
def generate_graph_and_update_scene(self):
try:
local_params=locals() #for error log get local paramters
this_function_name=sys._getframe().f_code.co_name #for error log get function name
self.vertex_dict.clear()
self.clear() #clear graphicsscene
self.graph_pos.clear() #clear graph position holder object
#function that generates the node data
root_nodes=my_database_query.get_nodes_information()
for node in root_nodes:
# add nodes to nx.graph object
self.nx_graph.add_node(node['column1'])
# create networkx graph
self.graph_pos = nx.spring_layout(self.nx_graph, iterations=25,scale=10)
for node in self.nx_graph.nodes(): # Add nodes to qgraphicsscene
v=default_nodeobject.my_ellipse(node,self.graph_pos)
self.addItem(v) # Add ellipse to qgraphics scene
for edge in self.nx_graph.edges():
self.addItem(defaultedgeview.edgeview(edge[0], edge[1],self.graph_pos))#add edges to qgraphicscene
except:
#Exception handler
message=str(sys.exc_info())
message=message + str(local_params)+" "+ str(this_function_name)
print message
This allows me to add say 600 'nodes' to my qgraphics scene, however when I clear the scene and add another say 1500 nodes, adding the items blocks the UI and my whole application freezes for a few seconds.
Also whenever I am doing things like looping through the graphicsitems say looking for the nodes that have a certain property, again the main thread freezes while I am looping,
Could anyone suggest a good method of keeping the UI responsive while things are being done to the grpahicsscene/items in the scene.
Ideally would like to have smooth, non-blocking updates to the scene, even when I have a few thousand items showing.

The problem here is the management of each node as a graphics item. Adding and removing to a scene, as well as rendering each item is going to take time. With this many items, I suggest designing it differently.
Consider the node graph as a single, custom graphics item which stores a group of nodes and manages them as a single unit, rather than 600+ separate items.
Designed this way, you only add one item to the scene (the node graph) which allows rapid addition and removal of nodes and you will also see a performance improvement in rendering the scene, as all nodes are drawn in one call to paint().
Of-course, if you need to move nodes around by clicking and dragging them, you'll have to add additional code to handle detecting which node is being selected in the item and move it yourself.
However, this is the optimal way to handle such a large number of items in a scene.

Related

JavaFX - How to add array of MeshViews to group or scene

Update: I updated my code to loop through the list of meshviews and add each to the group using group.getChildren.add(meshview[i]) but still does not show up on screen. Thanks.
I am trying to add an array list of MeshView type to a scene in JavaFx based GUI. I was able to get an initial example to work where it was one MeshView, but now I have a case where the data that is read in from a file results in an array of MeshView type. I could not find a "add" or "addAll" type function on the Group type to let me loop through all of the elements and add them and I could not get the Group constructor to let me add the list at ones in the arguments. I am using a Group to contain them because the over all GUI makes use of a BorderLayout defined using an FXML file. So my initial version adds the meshview to a group along with some point lights and then that group is added to the center of the border layout using the set method of it. Any help would be appreciated. Thanks.
Ps. I think I may have just found an answer. I forgot the add method is under the get children:
group.getChildren().addAll(meshView, pointLight);
as the line above from another answer shows. But I would still be interested in hearing the best ways because I still am confused on how to deal with sitaution where you have say 20 meshviews that make up a part to be shown on screen and you want to combine those and appropriate lights etc and scale to fit in center or borderlayout. I'm guessin I can first all all meshviews using add and then add the lights but was not sure. Thanks again.
You can always add mesh views to any Node type object like Group or BorderLayout ex.
root.getChildren().add(meshView);
you can add as most as you can to this root object and translate the meshView in the scene
meshView.setTranslateX(200);
meshView.setTranslateY(200);
meshView.setTranslateZ(200);
and set the camera and lightpoints configuration and add them as well to the scene

Qt: change QGraphicsItem receiver during mouse move

I am currently trying to implement a Bezier pen tool. The course of events looks like this:
click on point (QGraphicsItem), start moving while clicked
in QGraphicsScene mouseMoveEvent, prevent moves of point (with a boolean flag) until when distance from point.pos() to event.scenePos() reaches a threshold. When this happens unselect and mouseRelease point, add a node (QGraphicsItem) – select it and give it mousePress state (plus unset the boolean flag)
the user can move node after that, then release mouse.
(The node is a child item of the point.)
I tried to do this inside the scene’s mouseMoveEvent (I have a conditional branch to know when to do this):
point.setSelected(False)
point.ungrabMouse()
node.setPos(event.scenePos()-point.pos()) # positioning relative to point since it’s a childItem()
node.grabMouse()
event.accept()
But after doing this it occured that the node was only getting mouseMoveEvent’s after I release the mouse… (I print them in the console, the node itself did not move.)
So I figured, maybe the scene needs to eat a mouseReleaseEvent before sort of "releasing focus". I found an article that is tangent to the subject here.
So then instead of using ungrabMouse()/grabMouse(), I tried this:
mouseRelease = QEvent(QEvent.MouseButtonRelease)
self.sendEvent(point, mouseRelease)
node.setPos(event.scenePos()-point.pos()) # positioning relative to point since it’s a childItem()
mousePress = QEvent(QEvent.MouseButtonPress)
self.sendEvent(node, mousePress)
Now when I reach the distance threshold, I can see that only point gets selected (good) however as I move further both point and node are selected and moving… I would expect that since I have unselected and released (parent) point, it would not keep moving.
The article I linked to does do something different but it says "It turns out, we have to simulate a mouse release event to clear Qt’s internal state." which might be relevant to the current situation however I do not know what extra steps might need to be taken in order to “clear Qt’s internal state”… so I’m hoping a QGraphics aficionado can weigh in and help me out figuring this.
Thanks for having a look here.
A combination of sending mouse events and grabbing mouse manually works… has to be ungrabbed manually on mouseRelease though.

Artifacts showing when modifying a custom QGraphicsItem

I'm currently developping a small vector drawing program in wich you can create lines and modify them after creation (those lines are based on a custom QGraphicsItem). For instance, the picture below shows what happens when the leftmost (marked yellow) point of the line is dragged to the right of the screen, effectively lengthening the line :
Everything works fine when the point is moved slowly, however, when moved rapidly, some visual artifacts appear :
The piece of code I'm using to call for a repaint is located in the mouseMoveEvent redefined method, which holds the following lines of code :
QRectF br = boundingRect();
x2 = static_cast<int>(event->scenePos().x()-x());
y2 = static_cast<int>(event->scenePos().y()-y());
update(br);
There's apparently no problem with my boundingRect definition, since adding painter->drawRect(boundingRect()) in the paint method shows this :
And there are also no problem when the line is simply moved (flag QGraphicsItem::ItemIsMovable is set), even rapidly.
Does anyone know what is happening here ? My guess is that update is not being called immediately hence mouseMoveEvent can be called multiple times before a repaint occurs, maybe canceling previous calls ? I'm not sure.
Of course the easy fix is to set the viewport mode of the QGraphicsView object holding the line to QGraphicsView::FullViewportUpdate), but that is ugly (and slow).
Without seeing the full function for how you're updating the line, I would guess that you've omitted to call prepareGeometryChange() before updating the bounding rect of the items.
As the docs state: -
Prepares the item for a geometry change. Call this function before changing the bounding rect of an item to keep QGraphicsScene's index up to date.

How do I keep two parallel Qsplitters equal sizes?

I have a Qt widget with four partitions, separated with splitters.
The top level is a vertical splitter, changing the heights of two horizontal splitters, topSplitter and bottomSplitter.
How can I keep both horizontal splitters positions equal, as if it was just one horizontal splitter?
I looked at linking signal for splitterMoved, and connecting it to a slot on the other splitter but there are no equivalent slots in the splitter class.
This would obviously have to avoid the issue of an infinite loop where one splitter's position updates the second, which updates the first.
It's pretty simple. Initialization (splitter1 and splitter2 are the splitters that need to be syncronized):
connect(ui->splitter1, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()));
connect(ui->splitter2, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()));
The slot:
void MainWindow::splitterMoved() {
QSplitter* senderSplitter = static_cast<QSplitter*>(sender());
QSplitter* receiverSplitter = senderSplitter == ui->splitter1 ?
ui->splitter2 : ui->splitter1;
receiverSplitter->blockSignals(true);
receiverSplitter->setSizes(senderSplitter->sizes());
receiverSplitter->blockSignals(false);
}
blockSignals ensures that calls will not go to infinite recursion. Actually, setSizes doesn't cause emitting splitterMoved, so you can remove both blockSigals calls and the code will still work. However, there is no note about this in the documentation, so I wouldn't rely on that.
In the containing widget you can create slots to connect to the splitterMoved signal.
There needs to be one slot for each splitter, where it needs to check if the splitter is already the correct size (to avoid the infinite loop) then update the size if necessary.
I am only including one of the example slots and connections, but one will be needed for each splitter that needs to be linked.
Putting the following content into the new slots for updating the splitter positions.
QList<int> OtherSize,Current;
OtherSize=topSplitter->sizes();
Current=bottomSplitter->sizes();
if(OtherSize!=Current)
{
bottomSplitter->setSizes(OtherSize);
}
This will create two lists ready to hold the sizes for the splitters.
It gets the current sizes of both splitters, and compares them.
The comparison is necessary to avoid the infinite loop.
Then, if they are different, it sets the sizes to match that of the other splitter.
Connecting that slot to the appropriate splitterMoved signal should work.
This connection is used in the constructor of the containing widget (where you created the new slots).
connect(topSplitter,SIGNAL(splitterMoved(int,int)),this,SLOT(updateBottomSplitter()));
It is safe to ignore the position and index supplied by the signal, because we check the sizes in a different way in this slot.
This will set all sizes, so all splitter bars will match position.
If there are only two, it doesn't matter but if there are more than two, when any bar is moved, all will be updated to match.

Is there a bug in my code for populating a QTreeView?

I'm using PyQt 4.4.
It's best shown using some pictures. All nodes should have leafs from 0 to 99. They are being incrementally loaded using canFetchMore() and fetchMore(). But for some reason unknown for me this works only for the root node. (Picture 1)
If I collapse and expand a node it loads additional 10 values. (Picture 2 & 3)
It's also strange, that it loads 10 values, as the code loads only 5 per call to fetchMore(), meaning this gets called 2 times before the code stops to load more data.
I've written a small example to demonstrate the problem, just run it with python test.py.
http://snipt.org/lLh
Does anyone know what causes this error?
I took a look at the Qt source (v4.5, though I don't expect much difference between v4.4 and v4.5) for QAbstractItemView and QTreeView, and I don't think they support incremental lazy loading of child nodes.
QAbstractItemView has no notion of trees, so it only calls fetchMore() on the top most index. It calls fetchMore() when:
Geometry is updated
The scroll bars are moved
Rows are inserted
The current item is changed as a result of an autoscrolling drag & drop operation
QTreeView additionally calls fetchMore() when:
An item is expanded (this is essentially the only time it calls fetchMore() with a non-root index)
The view's layout needs to be relaid, such as with expandAll() and collapseAll()
I think the best solution would be to subclass QTreeView to make it call fetchMore() in the appropriate places and with the appropriate indices.

Resources