Simple hierarchical listing in PyQT
I'm trying to create a UI with 3 columns - when you select an item in the left column, the select item is passed to a function, and it returns the items for the middle column (and of course same for selecting items in the middle column)
Should be simple, but I can't find any easy way to do this..
I first tried QColumnView, as it seemed perfect.. however implementing the QAbstractItemModel seems excessively complicated, and I couldn't find any useful examples of this.
Next, since there is a fixed number of levels, I made three QListView, and modified this example QAbstractListModel
..however there seemed to be no useful signal I could use to trigger the updating of the other levels of the hierarchy. According to the PyQT docs, QListView only has the "indexesMoved" signal. There is also "clicked" and "pressed", however these didn't trigger when changing items with the k开发者_C百科eyboard
The last options is QListWidget, which would work as it has the required signals (itemChanged etc), but the way list items are created is a bit tedious (making QListWidgetItem with the parent set to the QListWidget instance)
Edit: QListWidget
does pretty much what I need:
self.first_column = QListWidget()
self.first_column.itemSelectionChanged.connect(self.update_second_column)
Any QAbastractItemView
has a QItemSelectionModel
accessible via the selectionModel
method.
The QItemSelectionModel has signals that may help you:
currentChanged ( const QModelIndex & current, const QModelIndex & previous )
currentColumnChanged ( const QModelIndex & current, const QModelIndex & previous )
currentRowChanged ( const QModelIndex & current, const QModelIndex & previous )
selectionChanged ( const QItemSelection & selected, const QItemSelection & deselected )
Hope it helps.
QListView
inherits from QAbstractItemView
(I think you knew this), so it gets a few signals, hopefully one (or a few) of them works for you.
This is working for me (connect signals when initializing your QMainWindow
or main QWidget
, as in the SaltyCrane example):
connect(your_list_view, SIGNAL("clicked(const QModelIndex&)"), handler_slot)
...
def handler_slot(idx):
#idx is a QModelIndex
#QModelIndex.data() returns a QVariant, Qt4's generic data container
celldata = idx.data()
#Choose the proper datatype to ask the QVariant for (e.g. QVariant.toInt())
actualvalue = celldata.toInt()
#QVariant.toInt() happens to return a tuple
print actualvalue[0]
Depending on the type of data in your model, you'll want to choose the right data type to ask QVariant
for.
The sneaky part here is getting the QListView
to tell you which cell was clicked (i.e. using clicked(const QModelIndex&)
vs clicked()
). I think I spent some time looking at the C++ documentation for Qt4 before I realized you could get more out of the signals.
From here, I would have the handler_slot()
call a setData()
method on the model for your second QListView
(using data generated by the function you originally planned to use).
I'd be glad to elaborate if I haven't quite answered your question.
Edit: Working with arrow keys
Hmm, it seems weird that there isn't a default QListView
signal for arrow movement, but we can make our own.
(This almost seems out-of-style for Qt4's signals and slots modus operandi)
QListView
reimplements a method keyPressEvent(self, QKeyEvent)
which acts as a callback function when certain keys are pressed. You can read more. We can use this to grab the keyevent(s) that we want, and emit our own signal.
class ArrowableListView(QListView):
def keyPressEvent(self, keyevent):
#Maintain original functionality by calling QListView's version
QListView.keyPressEvent(self, keyevent)
#QListView.selectedIndexes returns a list of selected cells, I'm just taking the first one here
idx = self.selectedIndexes()[0]
#We'll emit a signal that you can connect to your custom handler
self.emit(SIGNAL("my_signal"), idx)
Once a keypress occurs, we ask the QListView
what the selected indices (!) are. Of course, you can choose to filter out certain keys and choose whether to handle multiple selected cells (I think you can set a selection mode where this isn't possible, see QListView
documentation).
Now that we have the selected indices (list of QModelIndex
), we pass the first one (a QModelIndex
) along with our signal. Because we're defining this signal in python, we don't have to set a function prototype; I have no idea if this is bad style.
Now all you have to do is connect the signal to a handler:
self.connect(your_list_view, SIGNAL("my_signal"), handler_slot)
and write your handler.
I hope this isn't too nasty of a workaround.
精彩评论