How can I get right-click context menus for clicks in QTableView header?
Solution 1
Turned out to be simpler than I thought. In the same manner as I add the popup menu for the QTableView widget itself, I can just get the header from table object and then attach a context menu in the same way as I did with the regular context menu.
headers = self.tv.horizontalHeader()
headers.setContextMenuPolicy(Qt.CustomContextMenu)
headers.customContextMenuRequested.connect(self.header_popup)
Solution 2
There's another potentially more powerful way to do this if you take the step and inherit the view instead of simply composing it. Does custom context menu work here? Yes, but why does anything other than the view need to know about it? It also will help better shape your code to deal with other issues properly. Currently the implementation doesn't provide any encapsulation, cohesion or support separation of responsibility. In the end you will have one big blob which is the antithesis of good design. I mention this because you seem to be placing all of the GUI Logic in this ever growing main function, and its the reason you ended up putting the sort implementation inside your model, which makes no sense to me. (What if you have two views of the model, you are forcing them to be sorted in the same way)
Is it more code? Yes, but it gives you more power which I think is worth mentioning. Below I'm demonstrating how to handle the headers and also any given cell you want. Also note that in my implementation if some OTHER widget exists which also defines a context menu event handler it will potentially get a chance to have crack at handling the event after mine; so that if someone else adds a handler for only certain cases they can do so without complicating my code. Part of doing this is marking if you handled the event or not.
Enough of my rambling and thoughts here's the code:
#Alteration : instead of self.tv = QTableView...
self.tv = MyTableView()
....
# somewhere in your TableView object's __init__ method
# yeah IMHO you should be inheriting and thus extending TableView
class MyTableView(QTableView):
def __init__(self, parent = None):
super(MyTableView, self).__init__()
self.setContextMenuPolicy(Qt.DefaultContextMenu)
## uniform one for the horizontal headers.
self.horizontalHeader().setContextMenuPolicy(Qt.ActionsContextMenu)
''' Build a header action list once instead of every time they click it'''
doSomething = QAction("&DoSomething", self.verticalHeader(),
statusTip = "Do something uniformly for headerss",
triggered = SOME_FUNCTION
self.verticalHeader().addAction(doSomething)
...
return
def contextMenuEvent(self, event)
''' The super function that can handle each cell as you want it'''
handled = False
index = self.indexAt(event.pos())
menu = QMenu()
#an action for everyone
every = QAction("I'm for everyone", menu, triggered = FOO)
if index.column() == N: #treat the Nth column special row...
action_1 = QAction("Something Awesome", menu,
triggered = SOME_FUNCTION_TO_CALL )
action_2 = QAction("Something Else Awesome", menu,
triggered = SOME_OTHER_FUNCTION )
menu.addActions([action_1, action_2])
handled = True
pass
elif index.column() == SOME_OTHER_SPECIAL_COLUMN:
action_1 = QAction("Uh Oh", menu, triggered = YET_ANOTHER_FUNCTION)
menu.addActions([action_1])
handled = True
pass
if handled:
menu.addAction(every)
menu.exec_(event.globalPos())
event.accept() #TELL QT IVE HANDLED THIS THING
pass
else:
event.ignore() #GIVE SOMEONE ELSE A CHANCE TO HANDLE IT
pass
return
pass #end of class
c00kiemonster
Updated on July 04, 2022Comments
-
c00kiemonster almost 2 years
The sample code below (heavily influenced from here) has a right-click context menu that will appear as the user clicks the cells in the table. Is it possible to have a different right-click context menu for right-clicks in the header of the table? If so, how can I change the code to incorporate this?
import re import operator import os import sys from PyQt4.QtCore import * from PyQt4.QtGui import * def main(): app = QApplication(sys.argv) w = MyWindow() w.show() sys.exit(app.exec_()) class MyWindow(QWidget): def __init__(self, *args): QWidget.__init__(self, *args) self.tabledata = [('apple', 'red', 'small'), ('apple', 'red', 'medium'), ('apple', 'green', 'small'), ('banana', 'yellow', 'large')] self.header = ['fruit', 'color', 'size'] # create table self.createTable() # layout layout = QVBoxLayout() layout.addWidget(self.tv) self.setLayout(layout) def popup(self, pos): for i in self.tv.selectionModel().selection().indexes(): print i.row(), i.column() menu = QMenu() quitAction = menu.addAction("Quit") action = menu.exec_(self.mapToGlobal(pos)) if action == quitAction: qApp.quit() def createTable(self): # create the view self.tv = QTableView() self.tv.setStyleSheet("gridline-color: rgb(191, 191, 191)") self.tv.setContextMenuPolicy(Qt.CustomContextMenu) self.tv.customContextMenuRequested.connect(self.popup) # set the table model tm = MyTableModel(self.tabledata, self.header, self) self.tv.setModel(tm) # set the minimum size self.tv.setMinimumSize(400, 300) # hide grid self.tv.setShowGrid(True) # set the font font = QFont("Calibri (Body)", 12) self.tv.setFont(font) # hide vertical header vh = self.tv.verticalHeader() vh.setVisible(False) # set horizontal header properties hh = self.tv.horizontalHeader() hh.setStretchLastSection(True) # set column width to fit contents self.tv.resizeColumnsToContents() # set row height nrows = len(self.tabledata) for row in xrange(nrows): self.tv.setRowHeight(row, 18) # enable sorting self.tv.setSortingEnabled(True) return self.tv class MyTableModel(QAbstractTableModel): def __init__(self, datain, headerdata, parent=None, *args): """ datain: a list of lists headerdata: a list of strings """ QAbstractTableModel.__init__(self, parent, *args) self.arraydata = datain self.headerdata = headerdata def rowCount(self, parent): return len(self.arraydata) def columnCount(self, parent): return len(self.arraydata[0]) def data(self, index, role): if not index.isValid(): return QVariant() elif role != Qt.DisplayRole: return QVariant() return QVariant(self.arraydata[index.row()][index.column()]) def headerData(self, col, orientation, role): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return QVariant(self.headerdata[col]) return QVariant() def sort(self, Ncol, order): """Sort table by given column number. """ self.emit(SIGNAL("layoutAboutToBeChanged()")) self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol)) if order == Qt.DescendingOrder: self.arraydata.reverse() self.emit(SIGNAL("layoutChanged()")) if __name__ == "__main__": main()
-
UpAndAdam over 10 yearsI'm well aware that they are not required and are a no-op. For me (and the folks I work with) we find it greatly increases readability particularly since we hop back and forth from C/C++, and that is something PEP-8 advocates for. Personally I also like them so that style-formatting tools can correct indentation problems in a context free manner, code-generation tools can insert stubs for me with a finite end, and it makes merges easier when people rewrite something changing the levels of nesting.
-
NuclearPeon over 10 yearsOkay, thanks for explanation. I figured it was out of habit rather than ignorance, and I was curious to know why. Much appreciated.
-
UpAndAdam over 10 yearsalways happy to explain, though if you are curious you could always ask your question directly it is a Q&A site :-P
-
NuclearPeon over 10 yearsAlthough I could have messaged you directly, I figured someone else might be interested in knowing the answer as well lol. Thanks
-
UpAndAdam over 10 yearsI think you misunderstand and I see why from how I wrote it. By directly I'm only referring to the way you 'asked' in your comment. You didn't ask a question; you made a statement indirectly questioning what I was doing. Compare this to the direct question "I'm presuming you know that pass isn't required; are you using it out of habit or for another reason?" Much more likely to be: well received, for me to mark your comment as useful and not to make the recipient feel defensive. ( I agree good for others, how do you message directly? )