QPixmaps Performance using Python - python

I am currently trying to create a game object that changes pixmap whenever it moves via .setPos() in a QGraphicsScene. Since I'm new to all this, I'm not sure what the most performance-efficient methods are to cache pixmaps or to change images.
I've already looked at QPixmapCache and re-implementing the paint() function, but I'm still unsure what the best method is. This is the idea I've got at the moment:
class Object(QGraphicsPixmapItem):
def __init__(self):
super(Object, self).__init__()
self.state = 0
self.img = {
"pix1": QPixmap("pix1.png"),
"pix2": QPixmap("pix2.png"),
"pix3": QPixmap("pix3.png")}
def changePix(self):
if self.state == 0:
self.setPixmap(self.img["pix1"])
elif self.state == 1:
self.setPixmap(self.img["pix2"])
elif self.state == 2:
self.setPixmap(self.img["pix3"])
I would appreciate any advice or feedback I can get.

I've never had to use a QPixmapCache object to avoid any performance issue previously, but that's going to depend on what exactly you're doing. If you're just switching between 5 or so relatively small static/generated images (.png < 20kB), I would say it's not necessary. But if you're going to be doing something like a 2k paint buffer with an undo function, or some graph that would need to be regenerated after some paint event, you'll want some sort of caching in place. I refactored your code a bit as well to avoid hard-coding anything.
class Object(QGraphicsPixmapItem):
def __init__(self, *args):
super(Object, self).__init__()
self.img = [a for a in args if os.path.exists(a)]
def load_image(img_path, set_now=False):
if img_path not in self.img:
self.img.append(img_path)
if set_now:
self.change_state(img_path)
def change_state(img_path):
if img_name in self.img:
self.setPixmap(QPixmap(self.img[self.img.index(img_path)]))

Related

Why would win32gui.MoveWindow() return false?

I've been working on a Python program, in which I simple do not understand why using the function
win32gui.MoveWindow() returns false and prints "fail". It seems like the parameter types are correct, (i.x, i.y, i.w, i.h all are integers). Yet, the function fails to move the Window. I'm assuming that the function returning false most likely means that it failed to move the window to the provided position. Extra note: I'm providing the hwnd of Opera.exe, if somehow that's the cause of this.
if win32gui.MoveWindow(hwnd, int(i.x), int(i.y), int(i.w), int(i.h), True):
print("scs")
else:
print("fail")
I wonder why. I've used a for loop to loop through an array of custom class objects, (which is "i"), and the class is this:
class window(object):
def __init__(self, process):
self.x = 0
self.y = 0
self.w = 0
self.h = 0
self.process = process
And the hwnd I'm providing as the parameter isn't invalid either, as I'm
using win32gui.EnumWindows(fixWindows, None) where "fixWindows" is the function name. I've tried debugging the title of these hwnd's, and they don't seem wrong.
Thanks!
Edit: This function also didn't work as required.

Methods using methods in python

I know there are many questions that touch upon this area, but none have clearly answered the problem I'm facing (or perhaps I'm just really thick).
So I'm transferring functional python code to OOP python, and I have some code
class fake_class:
def __init__(self, data_type=None):
if data_type is None:
self.data_type = ""
else:
self.data_type = data_type
def printToLog(x='', *args):
if args:
x = x.format(*args)
else:
x = str(x)
logFile.write(x)
logFile.write('\n')
print(x)
if __name__ == '__main__':
main()
def main(self):
self.printToLog('this is just an example, for some fake code')
f = fake_class('data-xml-435')
# please appreciate that the full code is over 500 lines
# long that has been edited down for the sake of readability
I need the main method to be able to call other methods in the class, but no matter what I do, I cannot manage to allow it to do so. I have made printToLog into a classmethod, I have tried different ways of instantiating the fake_class, calling and all to no avail. The program complains that it doesn't know what printToLog is, what self is or what fake_class is!
So how might I call a method with another method within Python?
if __name__ == '__main__':
main()
does not make any sense with class. You just don't need them.
With that removed, you have to call main explicitly using the object you created.
f = fake_class('data-xml-435')
f.main() # or f.printToLog(with arguments) whichever is exciting you!
Again, printToLog is function of class, so you need a self:
def printToLog(self, x='', *args):
if args:
x = x.format(*args)
else:
x = str(x)
logFile.write(x)
logFile.write('\n')
print(x)

QGraphicsScene changing objects when selected

I have a QGraphicsScene containing some simple objects (in this simplified example circles) that I want to change into other objects (here squares) when selected. More specifically I'd like to have parent objects which don't draw themselves, they are drawn by their child objects, and under various circumstances, but in particular when the parent objects are selected, I'd like the set of child objects to change. This is a nice conceptual framework for the overall app I am working on.
So I've implemented this in PySide and I thought it was working fine: the circles change nicely into squares when you click on them.
Until I use RubberBandDrag selection in the view. This causes an instant segfault when the rubber band selection reaches the parent object and the selection changes. Presumably this is being triggered because the rubber band selection in QT is somehow keeping a pointer to the child item which is disappearing before the rubber band selection action is complete.
Simplified code below - test it by first clicking on the object (it changes nicely) then dragging over the object - segfault:
from PySide import QtCore,QtGui
class SceneObject(QtGui.QGraphicsItem):
def __init__(self, scene):
QtGui.QGraphicsItem.__init__(self, scene = scene)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtGui.QGraphicsItem.ItemHasNoContents, True)
self.updateContents()
def updateContents(self):
self.prepareGeometryChange()
for c in self.childItems():
self.scene().removeItem(c)
if self.isSelected():
shape_item = QtGui.QGraphicsRectItem()
else:
shape_item = QtGui.QGraphicsEllipseItem()
shape_item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, False)
shape_item.setFlag(QtGui.QGraphicsItem.ItemStacksBehindParent,True)
shape_item.setPen(QtGui.QPen("green"))
shape_item.setRect(QtCore.QRectF(0,0,10,10))
shape_item.setParentItem(self)
def itemChange(self, change, value):
if self.scene() != None:
if change == QtGui.QGraphicsItem.ItemSelectedHasChanged:
self.updateContents()
return
return super(SceneObject,self).itemChange(change, value)
def boundingRect(self):
return self.childrenBoundingRect()
class Visualiser(QtGui.QMainWindow):
def __init__(self):
super(Visualiser,self).__init__()
self.viewer = QtGui.QGraphicsView(self)
self.viewer.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
self.setCentralWidget(self.viewer)
self.viewer.setScene(QtGui.QGraphicsScene())
parent_item = SceneObject(self.viewer.scene())
parent_item.setPos(50,50)
app = QtGui.QApplication([])
mainwindow = Visualiser()
mainwindow.show()
app.exec_()
So questions:
Have I just made a mistake that can be straightforwardly fixed?
Or is removing objects from the scene not allowed when handling an ItemSelectedHasChanged event?
Is there a handy workaround? Or what's a good alternative approach? I could replace the QGraphicsRectItem with a custom item which can be drawn either as a square or a circle but that doesn't conveniently cover all my use cases. I can see that I could make that work but it will certainly not be as straightforward.
EDIT - Workaround:
It is possible to prevent this failing by preserving the about-to-be-deleted object for a while. This can be done by something like this:
def updateContents(self):
self.prepareGeometryChange()
self._temp_store = self.childItems()
for c in self.childItems():
self.scene().removeItem(c)
...
However, this is ugly code and increases the memory usage for no real benefit. Instead I have moved to using the QGraphicsScene.selectionChanged signal as suggested in this answer.
I've debugged it. Reproduced on Lunix
1 qFatal(const char *, ...) *plt 0x7f05d4e81c40
2 qt_assert qglobal.cpp 2054 0x7f05d4ea197e
3 QScopedPointer<QGraphicsItemPrivate, QScopedPointerDeleter<QGraphicsItemPrivate>>::operator-> qscopedpointer.h 112 0x7f05d2c767ec
4 QGraphicsItem::flags qgraphicsitem.cpp 1799 0x7f05d2c573b8
5 QGraphicsScene::setSelectionArea qgraphicsscene.cpp 2381 0x7f05d2c94893
6 QGraphicsView::mouseMoveEvent qgraphicsview.cpp 3257 0x7f05d2cca553
7 QGraphicsViewWrapper::mouseMoveEvent qgraphicsview_wrapper.cpp 1023 0x7f05d362be83
8 QWidget::event qwidget.cpp 8374 0x7f05d2570371
qt-everywhere-opensource-src-4.8.6/src/gui/graphicsview/qgraphicsscene.cpp:2381
void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode,
const QTransform &deviceTransform)
{
...
// Set all items in path to selected.
foreach (QGraphicsItem *item, items(path, mode, Qt::DescendingOrder, deviceTransform)) {
if (item->flags() & QGraphicsItem::ItemIsSelectable) { // item is invalid here
if (!item->isSelected())
changed = true;
unselectItems.remove(item);
item->setSelected(true);
}
}
They are using items() function to find a list of items under the rubber band selection. But if one item while processing deletes something the item pointer just becomes invalid. And next call to item->flags() causes the crash.
As alternative you could use QGraphicsScene::selectionChanged signal. It's emitted only once per selection change.
Looks like it's not expected by Qt to have some major changes in itemChange
Behind of this here is common mistake you have with prepareGeometryChange() call.
It's designed to be called right before changing boundingRect. Bounding rect should be the old one when prepareGeometryChange called and new one right after.
So that's could happen:
In updateContents:
self.prepareGeometryChange(); # calls boundingRect. old value returned
...
shape_item.setParentItem(self); # could call the boundingRect. but still old value returned!
After child added it calls boundingRect again but value unexpected different.
As a solution you can add a variable
def updateContents(self):
for c in self.childItems():
self.scene().removeItem(c)
if self.isSelected():
shape_item = QtGui.QGraphicsRectItem()
else:
shape_item = QtGui.QGraphicsEllipseItem()
shape_item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, False)
shape_item.setFlag(QtGui.QGraphicsItem.ItemStacksBehindParent,True)
shape_item.setPen(QtGui.QPen("green"))
shape_item.setRect(QtCore.QRectF(0,0,10,10))
shape_item.setParentItem(self)
self.prepareGeometryChange();
self._childRect = self.childrenBoundingRect()
def boundingRect(self):
return self._childRect

cx_Freeze Build of Tkinter Application Extremely Buggy

I recently created a small game using tkinter (python version 3.6.1) and froze it using cx_Freeze. The game has four buttons: an undo button, a restart button, a "find legal moves" button, and a "find best move button". The "find best move" button uses a shelve database to find the best move for the first three turns and a recursive function that traverses the move tree on the fly for the fourth turn and up. My code disables the buttons when they should not be used.
I made sure to include the necessary DLLs in the setup script and I was able to run the executable without errors. However, three of the buttons are disabled until the fourth turn (when the recursive function begins to be used) and the application is extremely buggy in many other ways. However, it works perfectly when I run the unfrozen version.
I honestly don't know what code snippets I would need to provide to you guys, as this issue has me utterly at a loss. The only clue I have is that the pyc files in the build differ in size from the unfrozen app. I know this is rather vague, but I do not know what specifics would be useful to give. Any help, if possible, would be greatly appreciated.
"Find best move" method:
def _find_best_move(self):
"""Finds best move possible for current game."""
if len(self.game.moves) <= 3:
with shelve.open("paths") as db:
best_paths = db[str(self.game.moves)]
best_path = choice(best_paths)
else:
self.path_finder(self.game)
best_path = self.path_finder.best_path
best_move = best_path[len(self.game.moves)]
best_move = (__class__._add_offset(best_move[0]), best_move[1])
return best_move
Updates Button State:
def update_gui(self):
"""Updates GUI to reflect current game conditions."""
legal_moves = self.game.find_legal_moves()
if self.game.moves:
self.undo_btn["state"] = "!disabled"
self.restart_btn["state"] = "!disabled"
self.best_move_btn["state"] = "!disabled"
else:
self.undo_btn["state"] = "disabled"
self.restart_btn["state"] = "disabled"
if legal_moves:
self.show_moves_btn["state"] = "!disabled"
else:
self.show_moves_btn["state"] = "disabled"
if legal_moves and self.game.moves:
self.best_move_btn["state"] = "!disabled"
else:
self.best_move_btn["state"] = "disabled"
My __init__ file:
initpath = os.path.dirname(__file__)
os.chdir(os.path.join(initpath, "data"))
PathFinder class (traverses move tree on the fly):
class PathFinder:
"""Provides methods to find move paths that meet various criteria.
Designed to be called after the player makes a move.
"""
_game = None
best_path = None
best_score = None
def __call__(self, game):
"""Call self as function."""
if not game:
self._game = DummyGame()
elif not isinstance(game, DummyGame):
self._game = DummyGame(game)
else:
self._game = game
moves = self._game.moves
self.possible_paths = dict.fromkeys(range(1,9))
root = Node(moves[-1])
self._find_paths(root)
self._find_paths.cache_clear()
found_scores = [score for score in self.possible_paths.keys() if
self.possible_paths[score]]
self.best_score = min(found_scores)
self.best_path = self.possible_paths[self.best_score]
#lru_cache(None)
def _find_paths(self, node):
"""Finds possible paths and records them in 'possible_paths'."""
legal_moves = self._game.find_legal_moves()
if not legal_moves:
score = self._game.peg_count
if not self.possible_paths[score]:
self.possible_paths[score] = self._game.moves.copy()
else:
children = []
for peg in legal_moves:
for move in legal_moves[peg]:
children.append(Node((peg, move)))
for child in children:
self._game.move(*child.data)
self._find_paths(child)
try:
self._game.undo()
except IndexError:
pass
Peg class:
class Peg(RawPen):
"""A specialized 'RawPen' that represents a peg."""
def __init__(self, start_point, graphics):
"""Initialize self. See help(type(self)) for accurate signature."""
self.graphics = graphics
self.possible_moves = []
super().__init__(self.graphics.canvas, "circle", _CFG["undobuffersize"],
True)
self.pen(pendown=False, speed=0, outline=2, fillcolor="red",
pencolor="black", stretchfactor=(1.25,1.25))
self.start_point = start_point
self.goto(start_point)
self.ondrag(self._remove)
self.onrelease(self._place)
def _remove(self, x, y):
"""Removes peg from hole if it has moves."""
if self.possible_moves:
self.goto(x,y)
def _place(self, x, y):
"""Places peg in peg hole if legal."""
if self.possible_moves:
target_holes = [tuple(map(add, self.start_point, move)) for move in
self.possible_moves]
distances = [self.distance(hole) for hole in target_holes]
hole_distances = dict(zip(distances, target_holes))
nearest_hole = hole_distances[min(hole_distances)]
if self.distance(nearest_hole) <= 0.45:
self.goto(nearest_hole)
peg = self.graphics._subtract_offset(self.start_point)
move = tuple(map(sub, self.pos(), self.start_point))
move = tuple(map(int, move))
self.graphics.game.move(peg, move)
self.start_point = self.pos()
else:
self.goto(self.start_point)
The frozen application is going to have a different value for __value__ then the unfrozen application. You will have to deal with that accordingly! This is a common issue that bites a lot of people. Anything that assumes that the module is found in the file system is going to stop working properly when frozen. The other gotcha is dynamic importing of modules.
The documentation covers this and other topics that will hopefully help you out!

How to correctly change the content of a DataViewModel

wxPython is giving me a lot of headaches lately, so I once again have to ask you guys here :)
My Setup
Windows 7
Portable Python v 2.7.6.1 (http://portablepython.com/wiki/PortablePython2.7.6.1/)
wxPython 3.0.2.0 (http://www.wxpython.org/)
The given code is a very boiled down version of my actual app. Actually, I have one big model, that is displayed in different controls in different manners.
Therefore, I have this one model, which is the modelRoot in the code example, from which I build different DataViewModels (MyDvcModel) for different DataViewCtrls. In the code example, I only have one DataViewModel and one DataViewCtrl, because it suffices to show my problem.
I tried to stick close to the DataViewModel example in https://github.com/svn2github/wxPython/blob/master/trunk/demo/DVC_DataViewModel.py
The Code
This is my minimal working example:
import wx
import wx.dataview
from wx.lib.pubsub import pub
#class for a single item
class DvcTreeItem(object):
def __init__(self, value='item'):
self.parent = None
self.children = []
self.value = value
def AddChild(self, dvcTreeItem):
self.children.append(dvcTreeItem)
dvcTreeItem.parent = self
def RemoveChild(self, dvcTreeItem):
self.children.remove(dvcTreeItem)
dvcTreeItem.parent = None
#class for the model
class MyDvcModel(wx.dataview.PyDataViewModel):
def __init__(self, root):
wx.dataview.PyDataViewModel.__init__(self)
self.root = root
pub.subscribe(self.OnItemAdded, 'ITEM_ADDED')
#-------------------- REQUIRED FUNCTIONS -----------------------------
def GetColumnCount(self):
return 1
def GetChildren(self, item, children):
if not item:
children.append(self.ObjectToItem(self.root))
return 1
else:
objct = self.ItemToObject(item)
for child in objct.children:
#print "GetChildren called. Items returned = " + str([child.value for child in objct.children])
children.append(self.ObjectToItem(child))
return len(objct.children)
def IsContainer(self, item):
if not item:
return True
else:
return (len(self.ItemToObject(item).children) != 0)
return False
def GetParent(self, item):
if not item:
return wx.dataview.NullDataViewItem
parentObj = self.ItemToObject(item).parent
if parentObj is None:
return wx.dataview.NullDataViewItem
else:
return self.ObjectToItem(parentObj)
def GetValue(self, item, col):
if not item:
return None
else:
return self.ItemToObject(item).value
#-------------------- CUSTOM FUNCTIONS -----------------------------
def OnItemAdded(self, obj):
self.Update(obj) #for some weird reason, the update function cannot be used directly as event handler for pub (?).
def Update(self, obj, currentItem=wx.dataview.DataViewItem()):
children = []
self.GetChildren(currentItem, children)
for child in children:
self.Update(obj, child) #recursively step through the tree to find the item that belongs to the added object
if self.ItemToObject(child) == obj:
self.ItemAdded(self.GetParent(child), child)
print "item " + obj.value + " was added!"
break
#class for the frame
class wxTreeAddMini(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT))
self.myDVC = wx.dataview.DataViewCtrl(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0)
self.myButton = wx.Button(self, wx.ID_ANY, u"Add Child", wx.DefaultPosition, wx.DefaultSize, 0)
self.myDelButton = wx.Button(self, wx.ID_ANY, u"Del Child", wx.DefaultPosition, wx.DefaultSize, 0)
mySizer = wx.BoxSizer(wx.VERTICAL)
mySizer.Add(self.myDVC, 1, wx.ALL|wx.EXPAND, 5)
mySizer.Add(self.myButton, 0, wx.ALL, 5)
mySizer.Add(self.myDelButton, 0, wx.ALL, 5)
self.SetSizer(mySizer)
app = wx.App(False)
modelRoot = DvcTreeItem('root')
child1 = DvcTreeItem('child1 - the forgotten one')
child1.AddChild(DvcTreeItem('even complete subtrees'))
child1.AddChild(DvcTreeItem('disappear'))
modelRoot.AddChild(child1)
modelRoot.AddChild(DvcTreeItem('child2 - the forgotten brother'))
childNum = 3
model = MyDvcModel(modelRoot)
frame = wxTreeAddMini(None)
frame.myDVC.AssociateModel(model)
frame.myDVC.AppendTextColumn("stuff", 0, width=250, mode=wx.dataview.DATAVIEW_CELL_INERT)
frame.Show()
def DeleteLastItemFromRoot(*ignoreEvent):
global childNum
if modelRoot.children != []:
obj = modelRoot.children[-1] #select last item
modelRoot.RemoveChild(obj)
model.ItemDeleted(model.ObjectToItem(modelRoot), model.ObjectToItem(obj))
def AddItemToRoot(*ignoreEvent):
global childNum
newObject = DvcTreeItem('child' + str(childNum))
modelRoot.AddChild(newObject)
childNum += 1
VARIANT = 'callItemAdded'
if VARIANT == 'viaMessage':
wx.CallAfter(pub.sendMessage, 'ITEM_ADDED', obj=newObject)
elif VARIANT == 'callItemAdded':
model.ItemAdded(model.ObjectToItem(modelRoot), model.ObjectToItem(newObject))
frame.myButton.Bind(wx.EVT_BUTTON, AddItemToRoot)
frame.myDelButton.Bind(wx.EVT_BUTTON, DeleteLastItemFromRoot)
app.MainLoop()
My Goal
My ultimate goal is to only update the low level model (modelRoot and its descendants/children) and have all DataViewModels being updated by that. Unfortunately, I have to call ItemAdded on each model, which is a pretty big pain (cause I have to do the same for deleting, editing and moving items).
Also, I don't know the item ID of the newly added object, because the item ID is different in each DataViewModel. Therefore, I use pub to send a message to all the DataViewModels, which then search for that new object and call ItemAdded on themselves respectively.
Since this didn't work out properly, I tried to call ItemAdded directly, which also doesn't work.
You can switch between both implementations by changing the value of the VARIANT variable. The final goal is to get the VARIANT 'viaMessage' to work.
The Problem
Here is a description of how to reproduce the weird behavior:
Start the application. You should only see the collapsed root item (with the '+' next to it). Without touching the tree view, just click the "Add Children" button a few times.
Now expand the root item. You will see that there are a few children in it (as many as how often you clicked). However, at program start, two children were added, which are now missing. This is not desired and I consider this wrong behavior.
EDIT: Okay, things got even weirder:
I edited the code and implemented an additional delete button. When I repeat everything until step 2. and then delete all the added children, the children 1 and 2 suddenly magically appear again! (left after adding 2 children, then expanding root || right after deleting the two added children and expanding root again)
Anyway, now please restart the application (close the window and run the script again). Now expand the root item and click "Add Children". Wow, suddenly it works.
Okay, let's try another one: Restart the application. Expand and collapse the root item again. Now click "Add Children" a few times. Now expand the root item again.
Again, it seemed to work. All children, the ones that were added at the beginning as well as the ones added by the button are there.
So the bug apparently only appears when children are added before you have ever expanded the parent item.
What kind of sorcery is this?
My impression is that what I want to achieve is nothing extraordinary and I'm wondering where the mistake is and that I can't find that problem via google, so I have to assume that the mistake is on my side, but I can't find it.
Only to justify the title of this question: I have similar problems when deleting an item. So, the question is more generally about how to correctly change the content of a DataViewModel (e.g. delete, add and change the value of an item) rather than just adding an item.
My Attempts
I tried to google for "wxwidgets dataviewmodel itemadded collapsed", but the results are not what I'm looking for.
I have an idea, which I haven't tried so far, because it would only be a workaround: On program start, I could once programmatically expand and collapse all subtrees. However, I would like to avoid that workaround.
I tried to debug it but couldn't see anything suspicious.
I checked the original wxWidgets code but didn't quite grasp it.
My Questions
What is wrong? Why doesn't it work as desired? Is this a wxPython bug or a bug in my code?
How can I fix it?
Side-Quests
I there a better way to achieve my goal than how I have implemented it?
Do you see any other flaws or drawbacks in my code? (except that it's a slimmed down version and I tried to avoid boilerplate as if __name__ == '__main__': main() and MVC design (at least C is missing) etc.)
Why can't I use MyDvcModel.Update as message handler directly but I have to use the indirection via OnItemAdded()? If I use MyDvcModel.Update, I get an exception before the app actually starts (TypeError: in method 'DataViewItem___cmp__', expected argument 2 of type 'wxDataViewItem *').
Would be nice, if these questions could also be answered, but it's neither necessary nor sufficient for me to accept your answer as solution ;)
Any help is appreciated.
Your PyDataViewModel code is more complicated than required for your application. Instead of Updateing your DVC model it is perfectly possible to just clear it (making the model itself figuring out how data has changed and sending messages to the DVCs depending on it). This works without noticeable delay for hundred items (i have not tested with several thousands).
Do as follows:
# remove subscription, no longer needed
# pub.subscribe(self.OnItemAdded, 'ITEM_ADDED')
# remove OnItemAdded and Update
#-------------------- CUSTOM FUNCTIONS -----------------------------
Simplify to:
def DeleteLastItemFromRoot(*ignoreEvent):
global childNum
if modelRoot.children != []:
obj = modelRoot.children[-1] #select last item
modelRoot.RemoveChild(obj)
# no longer required, handled my model.Cleared()
# model.ItemDeleted(model.ObjectToItem(modelRoot), model.ObjectToItem(obj))
# Forcing a synchronisation python model/PyDataViewModel/DVC
model.Cleared()
def AddItemToRoot(*ignoreEvent):
global childNum
newObject = DvcTreeItem('child' + str(childNum))
modelRoot.AddChild(newObject)
childNum += 1
# syncing
model.Cleared()

Categories