How gets QtreeView and TreeModel synchronized - python

I'm using pyqt5 and python 3.6.
I am confused about index functions in QModel/TreeView and ask here to clear my knot in brain. Some threads confused me more because they are very special. My question is more or less a basic one.
I have a model (based on Uludag's great tutorials) with data and did set it to my tree:
treeView = QTreeView()
treeView.show()
treeView.setModel(model)
#additional code
treeView.clicked.connect(treeViewStructure_clicked)
Now I have to prepare further action in the program based on the selected item. I can get the item by
def treeViewStructure_clicked(self):
#get the row of the click and print
index = treeView.currentIndex()
print(index.row())
print('selected item index at %s with data: %s' % (index.row(), index.data()))
So far so good. But the index is generated from treeView (model doesn't work). How can I connect the selection form the view to the model, so that I have the correct Modelindex and can change data in the model?
And of course, how to write them back that they will get synchronized? In other words, which index do I need for what and where and where do they come from?

I would love to leave a comment but I don't have enough reputation.
Since your implementation of model did not include a method that could return the selected index, I have to get it from pyqt5. Since treeView is a QTreeView, you can:
treeView.selectionModel().selectedIndexes()
the information is from here.
These will return a QModelIndex list, which has an api here.
(note that although there are code examples on this website, it's in C++, but it should still be readable)
In the api, you can get the row and column as well as parent from the QModelIndex object.
Normally, I'd just modify the data via the object returned by treeView.selectionModel().selectedIndexes().
for example:
arr = treeView.selectionModel().selectedIndexes()
index = arr[0]
then just modify index.
but if you MUST use your own model for whatever reason, there is one thing you can do.
Since it extends QAbstractItemModel, you can use the index function.
For example:
arr = treeView.selectionModel().selectedIndexes()
indexTemp = arr[0]
index = model.index(indexTemp.row(), indexTemp.column(), indexTemp.parent())
then modify the value through setData funtion
But as you can see, it's an extra step to get the same object

Related

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])

Access gtk.TreeIter inside render() Function of gtk.CellRenderer -- pygtk

I am trying to code the following: Two Columns. One contains a itemId, the other one contains a typeId. I want to render the itemId only when the typeId equals a specific value.
class IDRenderer(gtk.CellRendererText):
def __init__(self):
gtk.CellRendererText.__init__(self)
def do_render(self,window, widget, background_area, cell_area, expose_area, flags):
if ----} Condition to ask for value of the typeId - Cell {-----:
gtk.CellRendererText.do_render(self, window, widget, background_area, cell_area,
expose_area, flags)
gobject.type_register(IDRenderer)
I don't know how to get the iter of the currently rendered row which i need to determine the value of the typeId. Is this even possible?
I now found out, thanks to a nice guy on #pygtk on gimpIRC:
You can do that, with binding so called cell data functions to the corresponding gtk.TreeViewColumn as done here in this example
def renderId(celllayout, cell, model, iter):
if model.get_value(iter,1) == 3:
cell.set_property('visible',True)
else:
cell.set_property('visible',False)
treeviewcolumn = gtk.TreeViewColumn()
renderer = gtk.CellRendererText()
treeviewcolumn.add_attribute(renderer,'text',0)
treeviewcolumn.set_cell_data_func(renderer,renderId)
I ommited some code relevant to render a complete treeview, but i think it shows what i wanted to do and how to do it.
The column renderes the value in the first column (0) of the model only if the value in the second modelcolumn (1) equals 3
I hope this could help someone some time.
It's not possible as far as I know. You need to use properties of the custom renderer which will be set automatically by the code calling the rendering function. (Like the text property of CellRendererText -- the rendering code doesn't get the text from the tree model, but the tree model sets the text property of the renderer before calling the rendering code.)

Python object in QMimeData

I'm implementing drag and drop QTreeView based on my custom model. All works fine, my tree displays data, drag and drop is enabled and now the last step lies ahead of me - to drop and trasfer dragged data. To do this I need to implement mimeTypes, mimeData and dropMimeData methods in my model. And now my question: Is there any easy standard way how to pass an arbitrary Python object through QMimeData? I'm doing just an internal move within QTreeView which displays hierarchy of my Python classes Person. And I want to reorder them. No drag and drop outside the application, not even outside of control. I have found only single one tutorial: link text. But is it the only way? Cannot it be done without encoding the Python object into ByteArray. I need really simple solution for my only one class Person. Thank you.
Do not try to implement drag and drop by reparenting the underlying python object. This won't work if the drag comes from outside your process; nor will it work for a copy operation (your node objects probably cannot exist in multiple places in the tree).
Think of a drag and drop "move" as three operations:
serialize the data to some byte string
deserialize into a new index (or new indexes)
(optional: if "move" rather than "copy") remove the old index(es)
mineData() and dropMimeData() are the serialize and deserialize operations that you provide. Python provides some easy ways to implement them -- check the documentation for the pickle module. If you're lucky, pickle.dumps() and pickle.loads() will work out-of-the-box for you.
Edit: I couldn't figure out how to paste code in comments, so here's the solution my comment refers to. This is safe, in the sense that it will fail by throwing a KeyError instead of causing crashes if you happen to break your rules.
# drag: store off the data in a safe place, and serialize a cooky
# that the drop target can use to retrieve the data.
self.__tmp_storage_dct = { self.__tmp_storage_cooky: stuff }
m.setData(self.rowlistptr_mime_type, QByteArray(pickle.dumps(self.__tmp_storage_cooky)))
self.__tmp_storage_cooky += 1
# drop:
if mime.hasFormat(self.rowlistptr_mime_type):
print "got tmpstorage"
cooky = pickle.loads(mime.data(self.rowlistptr_mime_type).data())
nodes = self.__tmp_storage_dct.pop(cooky)
Ok, I think I have a possible solution for you.
Keep in mind that I am a complete neophyte in this area so no warranties that his a) works b) is a decent solution c) won't make a "real" programmer toss their lunch.
What I did was convert the entire ancestor tree of a particular item into a text list of row column pairs. (i.e. list the row and column of the dragged item, the row and column of its parent, the row and column of its parent's parent, etc... till we get to an invalid index - i.e. the root)
This looks something like this (this example shows that the dragged item is four levels deep):
2;0,1;0,5;0,1,0
^ ^ ^ ^
| | | |
| | | great grandparent (and child of the root item)
| | |
| | grandparent
| |
| parent
|
item being dragged
Later, in the dropMimeData function, I reverse the list (so that it reads from the root back down to the item being dragged) and build the indexes one at a time till I get back to the originally dragged item.
Here are the snippets of code that make that all work. Again, I can't warrantee that this is a good idea, just that it appears to work and does not require that you serialize your python objects into a ByteArray.
Hope this helps.
#---------------------------------------------------------------------------
def mimeTypes(self):
"""
Only accept the internal custom drop type which is plain text
"""
types = QtCore.QStringList()
types.append('text/plain')
return types
#---------------------------------------------------------------------------
def mimeData(self, index):
"""
Wrap the index up as a list of rows and columns of each
parent/grandparent/etc
"""
rc = ""
theIndex = index[0] #<- for testing purposes we only deal with 1st item
while theIndex.isValid():
rc = rc + str(theIndex.row()) + ";" + str(theIndex.column())
theIndex = self.parent(theIndex)
if theIndex.isValid():
rc = rc + ","
mimeData = QtCore.QMimeData()
mimeData.setText(rc)
return mimeData
#---------------------------------------------------------------------------
def dropMimeData(self, data, action, row, column, parentIndex):
"""
Extract the whole ancestor list of rows and columns and rebuild the
index item that was originally dragged
"""
if action == QtCore.Qt.IgnoreAction:
return True
if data.hasText():
ancestorL = str(data.text()).split(",")
ancestorL.reverse() #<- stored from the child up, we read from ancestor down
pIndex = QtCore.QModelIndex()
for ancestor in ancestorL:
srcRow = int(ancestor.split(";")[0])
srcCol = int(ancestor.split(";")[1])
itemIndex = self.index(srcRow, srcCol, pIndex)
pIndex = itemIndex
print itemIndex.internalPointer().get_name()
return True

PyQt4: My database displays empty cells

I am using the pyqt4 framework to do some displays for database forms. Unfortunately, I hit a snag while trying to filter and display my database by last name. Assume that the database connection works. Also assume that I have the correct amount of items in my tupleHeader since I use the same initializeModel method for other methods (like the search() function described below, and it works fine.
I call the display() function and it works perfectly fine, but when creating a proxyModel from the sourceModel, and trying to display the proxyModel with my search function, I have empty cells displayed. When I restrict my search so that it filters half my database, it shows that many cells (so most of this is working). But it will not display anything from the database itself.
Below is some of my code:
from PyQt4 import QtGui, QtCore, QtSql
self.caseSensitivity = QtCore.Qt.CaseInsensitive
self.syntax = QtCore.QRegExp.FixedString
def initializeModel(self, model):
model.setTable(self.table)
#model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
b = 0
for a in self.tupleHeader:
model.setHeaderData(b, QtCore.Qt.Horizontal, QtGui.qApp.tr(a))
b += 1
model.select()
def display(self):
'''reads all row data and displays it on a tableview'''
self.connectdb(self.db, self.localhost, self.dbname, self.username, self.password)
model = QtSql.QSqlTableModel()
self.initializeModel(model)
self.view.setModel(model)
self.disconnectdb(self.db)
def search(self, searchQuery):
'''queries database data, filters it, and displays it on a tableview'''
sourceModel = QtSql.QSqlTableModel()
proxyModel = QtGui.QSortFilterProxyModel()
self.initializeModel(sourceModel)
proxyModel.setSourceModel(sourceModel) # allows to edit proxyModel without changing underying model
#searchQuery contains the last name that I am filtering with
regExp = QtCore.QRegExp(searchQuery, self.caseSensitivity, self.syntax)
proxyModel.setFilterRegExp(regExp)
proxyModel.setFilterKeyColumn(2) # this column holds the last names
# self.view contains the table itemview my application uses to display the database
self.view.setModel(proxyModel)
EDIT: I am not interested in keeping this piece of code, I just want to know why it allows the table to show the table's content instead of a bunch of empty cells
print self.proxyModel.filterAcceptsRow(2, self.sourceModel)
Also, if you put in this after the last statement ( self.view.setModel(proxyModel) ), it will show the table, even if it does send an error:
print self.proxyModel.filterAcceptsRow(2, self.sourceModel)
TypeError: QSortFilterProxyModel.filterAcceptsRow(int, QModelIndex): argument 2 has unexpected type 'QSqlTableModel'
It doesn't matter what the arguments are or whether I use filterAcceptsRow ro filterAcceptsColumn, it displays the table. Does this narrow down the problem some?
Thank you for your time searching for this coding error/bug, and happy hunting!
While I could not find the solution to my problem, it solved itself. I am not certain, but I think it was this code snippet that made it work.
self.dbmanip = CoreDB(self.userTableView, self.table)
This was put inside of the SetupUi() method created by the Qt4 Designer. I think either the dbmanip that contained the TableView lost the information from the proxyModel, or (more likely), I may have referenced the wrong table between the proxyModel and the original Model (that created the proxyModel), and then couldn't display because it was calling the cell structure from one table and the actual information from another.
These are all guesses though. Still, problem solved.

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.

Categories