TreeItemId obtained from TreeEvent is not the same as obtained from AppendItem - python

I am building a program that will store some complex objects, and I am using wxPython for the UI. The objects hierarchy will have a tree representation (TreeCtrl). I am using a dictionary to map objects from the UI to the database, using the TreeItemIds returned by AppendItem as keys and the objects themselves as values (actually I am not using the objects as values, but it simplifies the problem). The following snippet exemplifies what I am trying to do:
import wx
class ComplexObject(object):
def __init__(self, name, otherdata):
self.name = name
self.otherdata = otherdata
class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super(TestFrame, self).__init__(*args, **kwargs)
self.tree = wx.TreeCtrl(self)
self.rootid = self.tree.AddRoot("Root")
self.tree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.onrightclick)
self.objectmap = {}
def addobject(self, obj):
itemid = self.tree.AppendItem(self.rootid, obj.name)
self.objectmap[itemid] = obj
def onrightclick(self, event):
itemid = event.GetItem()
if itemid == self.rootid:
return
obj = self.objectmap[itemid]
print "Do something with ComplexObject {}".format(obj.name)
if __name__ == '__main__':
app = wx.App(False)
testframe = TestFrame(None)
for i in range(3):
obj = ComplexObject('obj{}'.format(i), i)
testframe.addobject(obj)
testframe.Show()
app.MainLoop()
When I right-click an entry in the tree I get a KeyError, because the object I get from the event (itemid = event.GetItem()) is not the same I get when I add an item (itemid = self.tree.AppendItem(self.rootid, obj.name)). Is this the expected behavior? How should I proceed to achieve what I am trying to do? I am starting to experiment with SetPyData and GetPyData, but I hope there is a better way to do that.
Thank you very much.
Platform Information: MS Windows 7, Python 2.7.9, wxPython 2.8.12.1

Yes, it is expected.
You can think of the TreeItemId as a closed box with an implementation dependent handle inside, and that the treectrl is the only one that can open the box to get the handle out. You may see different boxes at different times for the same tree item, but they will all have the same handle inside. But since the handle itself is an implementation detail there is no program access to it.
Using SetPyData to associate data to tree items is the proper way to do things like this. If you want to keep your associated data in a separate dictionary then you could generate unique dictionary keys when adding the items, and then pass they key to SetPyData, and use GetPyData to fetch the key later when you need to fetch the value object from the dictionary.

Related

Find item in QApplication by only the objectname

i want to find any object by a objectname string name inside of the QApplication
Something like
QApplication.instance().findByClassName("codeEditor")
which should return a list of widgets with this classname that i can iterate over if there is more then one
[QPushButton (QPushButton at: 0x0000008EA3B3DD80), QWidget (QWidget at: 0x0000008EA3F33F40)]
I have read this but it requires a object and i want something like *
This is something i came up with for testing:
def findWidget(name):
name = name.lower()
widgets = self.topLevelWidgets()
widgets = widgets + self.allWidgets()
ret = dict()
c = 0
for x in widgets:
c += 1
if name in x.objectName.lower() or name in str(x.__class__).lower():
ret["class:"+str(x.__class__)+str(c)] = "obj:"+x.objectName;continue
if hasattr(x, "text"):
if name in x.text.lower():
ret["class:"+str(x.__class__)+str(c)] = "obj:"+x.objectName
return ret
It doesn't even find the 'InfoFrame' which is clearly there:
>>> widget("info")

{}
I came up with this which works quite well
def getWidgetByClassName(name):
widgets = QApplication.instance().topLevelWidgets()
widgets = widgets + QApplication.instance().allWidgets()
for x in widgets:
if name in str(x.__class__).replace("<class '","").replace("'>",""):
return x
def getWidgetByObjectName(name):
widgets = QApplication.instance().topLevelWidgets()
widgets = widgets + QApplication.instance().allWidgets()
for x in widgets:
if str(x.objectName) == name:
return x
def getObjects(name, cls=True):
import gc
objects = []
for obj in gc.get_objects():
if (isinstance(obj, PythonQt.private.QObject) and
((cls and obj.inherits(name)) or
(not cls and obj.objectName() == name))):
objects.append(obj)
return objects
In Python, this can be done for any class using the gc module. It provides a method for retrieving the references of all objects tracked by the garbage-collector. This is obviously a quite inefficient approach, but it does (almost) guarantee that any type of object can be found.
Here's a function to get a list of all QObject instances either by class-name or object-name:
def getObjects(name, cls=True):
objects = []
for obj in gc.get_objects():
if (isinstance(obj, QtCore.QObject) and
((cls and obj.inherits(name)) or
(not cls and obj.objectName() == name))):
objects.append(obj)
return objects
This is only really a debugging tool, though - for a large application, there could easily be several hundred thousand objects to check.
If you only need objects which are subclasses of QWidget, use this function:
def getWidgets(name, cls=True):
widgets = []
for widget in QtGui.QApplication.allWidgets():
if ((cls and widget.inherits(name)) or
(not cls and widget.objectName() == name)):
widgets.append(widget)
return widgets
PS:
If you want to find all objects which are subclasses of QObject, this can only be achieved if you can somehow ensure that all the instances in your application have a valid parent (which, by definition, must also be a QObject). With that in place, you can then use root_object.findChildren(QObject) to get the full list. It is also possible to use findChild or findChildren to search for individual objects by object-name (optionally using a regular-expression, if desirable).
It is not possible to find all QObject instances in general. Qt does not keep track of them since objects can be used in multiple threads and the overhead of tracking them would be unnecessarily high.
Qt does keep track of all widgets, though, since widgets can only exist in the main thread, and they are fairly heavy objects so tracking them has comparably little overhead.
So, you could search all widgets you get from QApp.allWidgets(), and all of their children. You can also look through children of objects you otherwise have access to. But if a given object is parentless, or is not owned by a widget, then you won't find it that way.

How to change Operator's label in Blender 2.63 depending on the context?

I'm writing an exporter for a game my friend and I are making and it involves setting custom properties and tags to objects which are then recognized in the game and dealt with accordingly. Our engine, which is written in C/C++ has been successfully tested with my current version of the export script, and I''m currently working on tidying it up.
The script uses Blender's feature of custom properties to write custom tags to output file. The model typically consists of multiple 'parts' (Blender mesh objects that are parented to form a tree, with one 'parent' and multiple 'child' objects) and some of those parts are simple Blender Empty objects (for only it's X, Y and Z coordinates are needed) with custom properties that mark where things like ship's propulsion (it's a 3D shooter) are placed, or where the flames/explosions appear when ship's been shot. Those empty parts are also parented to either 'root' object or any of it's children. So far it's been working good, I have written a generic Operator class and some extended classes that reside in a panel which set part's properties (pretty handy since you don't have to add those custom properties by hand).
Now I want to speed thing up even more, that is to be able to simply click on an operator of desired type, and it should automatically add it to the scene and parent it to the active/selected object. I know how to do that, but I can't get those operators to change their labels. Basically, what I want is to operator to say 'Bullet point' when an existing empty is selected (I've got that part done), and when there's a mesh object selected to say 'Add bullet point'. So I just need a way to dynamically change operators' labels depending on the context (as the title of the question states clearly :))
This is what I got so far:
class OBJECT_OT_tg_generic (bpy.types.Operator):
bl_label = "Sets Generic Part Type"
bl_idname = "rbm.set_generic_part_type"
OB_TYPE = None
#classmethod
def poll (cls, context):
act = context.active_object
if 'Type' in act.keys ():
if act['Type'] == cls.OB_TYPE:
cls.bl_label = 'foo'
print (cls.bl_label)
# this prints foo but doesn't change the label
return False
return True
def execute (self, context):
# TODO: add code to automatically place empties and parent them to active object
bpy.context.active_object['Type'] = self.OB_TYPE
return{"FINISHED"}
And an example of a subclass:
class OBJECT_OT_tg_bullet_point (OBJECT_OT_tg_generic):
bl_label = "Bullet Point"
bl_idname = "rbm.set_bullet_point"
OB_TYPE = OT_BULLET_POINT
Here's how it looks in Blender:
http://i.imgur.com/46RAS.png
Guess I solved it. When you're adding an operator to a panel, you can do something like this:
def draw (self, context):
layout = self.layout
row = layout.row()
row.operator("foo.bar", text="Whatever you want")
and the "Whatever you want" is going to be your button's label. But what I did was something else. I didn't change the operators' labels, but instead gave them a different icons depending on whether it's a mesh or an empty currently selected/active:
def draw (self, context):
# (...) we're skipping some code here, obviously
act = context.active_object
if act.type == 'MESH':
op_icon = 'ZOOMIN'
else:
op_icon = 'EMPTY_DATA'
row = layout.column(align=True)
row.operator('rbm.set_bullet_point', icon=op_icon)
row.operator('rbm.set_rocket_point', icon=op_icon)
# (...) rest of the code

Getting the currently selected item in QTreeView

I have a number of items in a QTreeView. Each item is generated using this class:
class Branch(QStandardItem):
def __init__(self, label, uri = None):
QStandardItem.__init__(self, label)
self.uri = uri
This is my actual tree:
class FileTree(QTreeView):
def __init__(self):
QTreeView.__init__(self)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return:
crawler = self.selectedIndexes()[0].model().item(self.selectedIndexes()[0].row())
print(crawler.uri)
QTreeView.keyPressEvent(self, event)
As you can see, I'm a little unsure as to how to get the uri variable from the selected item. I found that selectedIndexes() returns a model and not the item itself. I'm not sure how to get from one to the other. Trying to get the item number using self.selectedIndexes()[0].row() was a bit of a shot in the dark, but it seems to ignore the various branches in the tree (for instance, it will give me a 0 for the first row in a branch, but won't tell me anything about what branch it's in).
What's the proper way to get the selected item from the QTreeView? Or is there a better way of detecting the spacebar or return keys being hit that would make this easier? There's a severe lack of Python documentation for Qt, so it's really hard to know if I'm ever doing things in a sensical manner.
You are calling the right function, it actually returns a QModelIndexList which is just a typedef for QList<QModelIndex> with the QModelIndex being the data structure that can point to any part of the tree. QModelIndex is not a Model in the sense of Model View Controller (MVC) but an adress of an object in a QAbstractItemModel which is the datastructure under all of Qt's ItemView objects, including your tree. You are actually pretty close, QAbstractModelIndex consists of a row, a column and a parent, which lets it adress any position in a hierarchical data structure. If you use the line
index = self.selectedIndexes()[0]
crawler = index.model().itemFromIndex(index)
you should get to the object that you are looking for.
As for documentation, even though there is no python specific documentation it helps to read through the official Qt documentation, the class hierarchy and functionality is still the same. There is very little C++ specific information in the docs.
Harald's answer didn't work for me, because I'm using a QSqlQueryModel as the model (I got the error {AttributeError}'QSqlQueryModel' object has no attribute 'itemFromIndex').
The below did the trick for me though, to get the 0th column data of the selected row:
dbQueryModel.itemData(treeView.selectedIndexes()[0])

ListCtrl - wxPython / Python

My question is if we can assign/bind some value to a certain item and hide that value(or if we can do the same thing in another way).
Example: Lets say the columns on ListCtrl are "Name" and "Description":
self.lc = wx.ListCtrl(self, -1, style=wx.LC_REPORT)
self.lc.InsertColumn(0, 'Name')
self.lc.InsertColumn(1, 'Description')
And when I add a item I want them to show the Name parameter and the description:
num_items = self.lc.GetItemCount()
self.lc.InsertStringItem(num_items, "Randomname")
self.lc.SetStringItem(num_items, 1, "Some description here")
Now what I want to do is basically assign something to that item that is not shown so I can access later on the app.
So I would like to add something that is not shown on the app but is on the item value like:
hiddendescription = "Somerandomthing"
Still didn't undestand? Well lets say I add a button to add a item with some other TextCtrls to set the parameters and the TextCtrls parameters are:
"Name"
"Description"
"Hiddendescription"
So then the user fills this textctrls out and clicks the button to create the item, and I basically want only to show the Name and Description and hide the "HiddenDescription" but to do it so I can use it later.
Sorry for explaining more than 1 time on this post but I want to make sure you understand what I pretend to do.
Instead of using the ListCtrl as your data structure, you could keep a separate list/dict of objects that contain all the information you want and refresh the ListCtrl from your other data structure.
For example:
class MyObject(object):
def __init__(self, name, description, hidden_description):
self.name = name
self.description = description
self.hidden_description = hidden_description
Then in your application:
def __init__(self):
self.my_items = {}
self.lc = wx.ListCtrl(self, -1, style=wx.LC_REPORT)
self.lc.InsertColumn(0, 'Name')
self.lc.InsertColumn(1, 'Description')
def addItemToMyListCtrl(self, name, description, hidden):
new_item = MyObject(name, description, hidden)
self.my_items[name] = new_item
self.lc.Append((new_item.name, new_item.description))
Then when you want to use your additional data you can just look up the correct item in the dictionary and your data will be there.
the wxListCtrl lets you associate arbitrary data with an item, that will not be displayed - read the docs for the following methods:
SetItemData
GetItemData
FindItemData
The wxListItem class also has GetData and SetData methods.
You could always set the width of the hidden column to zero, that might accomplish what you want. I just tried it in a C++ (non-wx) program and it worked fine.
wx.ListCtrl doesn't let you associate a python object with an item like wx.TreeCtrl does with its extremely useful SetPyData() method (and corresponding GetPyData()).
I haven't tried it myself, but there is code here that shows how to create a class to mix in python data with a list. Although I'll admit, it's not clear to me how you're meant to use it.
It also might be possible to directly inherit from wx.ListCtrl, and add the appropriate methods, but I haven't seen any attempts at that anywhere, so it may be harder than I'm thinking.
Alternately you can just use SetItemData() to store an int in each item, and use that int to index a dict (or list, if the items are ordered reliably and consistently) that contains the associated objects. tgray has already shown how to do this, and it's listed at the page I link above as well, so I won't go over it again.

Pyqt - QMenu dynamically populated and clicked

I need to be able to know what item I've clicked in a dynamically generated menu system. I only want to know what I've clicked on, even if it's simply a string representation.
def populateShotInfoMenus(self):
self.menuFilms = QMenu()
films = self.getList()
for film in films:
menuItem_Film = self.menuFilms.addAction(film)
self.connect(menuItem_Film, SIGNAL('triggered()'), self.onFilmSet)
self.menuFilms.addAction(menuItem_Film)
def onFilmRightClick(self, value):
self.menuFilms.exec_(self.group1_inputFilm.mapToGlobal(value))
def onFilmSet(self, value):
print 'Menu Clicked ', value
Instead of using onFilmSet directly as the receiver of your connection, use a lambda function so you can pass additional parameters:
receiver = lambda film=film: self.onFilmSet(self, film)
self.connect(menuItem_Film, SIGNAL('triggered()'), receiver)
I found this answer here for dealing with this issue in PyQt5, python3. I don't like it, the bVal variable to be precise, as I don't fully understand it but it took a long time to find so I thought I'd share it here. The bVal picks up the boolean value from triggered and allows the taskType to be passed.
self.taskMenu = QtGui.QMenu("Task")
self.tasks = self.getTasks() #FETCHES A LIST OF LIST
self.menuTasks = QtGui.QMenu()
for item in self.tasks:
menuItem_Task = self.taskMenu.addAction(item[1])
receiver = lambda: bVal, taskType=item: self.setTask(bVal, taskType)
menuItem_Task.triggered.connect(receiver)
self.taskMenu.addAction(menuItem_Task)
def setTask(self, ignore_bVal, taskType):
print taskType
Take a look at the Qt's property system. You can dynamically add a property containing a string or anything you desire, which defines the action. Then you can use sender() method in the slot to obtain the QObject calling the slot. Then, query the property you set and do whatever you want accordingly.
But, this is not the best method to do this. Using sender() is not advised because it violates the object oriented principle of modularity.
The best method would be using QSignalMapper class. This class maps signals from different objects to the same slot with different arguments.
I haven't used PyQt therefore i cannot give you exact syntax or an example, but it shouldn't be hard to find with a little research.
I was trying to figure out a similar issue and after looking at the code above this is what worked for me. Thought it would be good to show it all together. =)
self.taskMenu = QtGui.QMenu("Task")
self.tasks = self.getTasks() #FETCHES A LIST OF LIST
self.menuTasks = QtGui.QMenu()
for item in self.tasks:
menuItem_Task = self.taskMenu.addAction(item[1])
receiver = lambda taskType=item[0]: self.setTask(taskType)
self.connect(menuItem_Task, QtCore.SIGNAL('triggered()'), receiver)
self.taskMenu.addAction(menuItem_Task)
def setTask(self,taskType):
print taskType
Just some additional information,
I don't know why, but lambda function doesn't work with new pyqt syntax for connections :
Example of code not working :
self.contextTreeMenuAssignTo = QtGui.QMenu(self)
actionAssign = contextMenu.addMenu( self.contextTreeMenuAssignTo )
actionAssign.setText("Assign to : ")
for user in self.whoCanBeAssignated() :
actionAssignTo = QtGui.QAction( user[0] ,self)
self.contextTreeMenuAssignTo.addAction( actionAssignTo )
actionAssignTo.triggered.connect( lambda userID = user[1] : self.assignAllTo( userID ) )
But if you subsitute the last line with the old style connection syntax :
self.connect(actionAssignTo, QtCore.SIGNAL('triggered()'), lambda userID = user[1] : self.assignAllTo( userID ) )
Everything is fine.
With the new connection syntax, you only get the last element of the loop :(

Categories