如何在QTableView表头中右键单击获取上下文菜单?

12
下面的示例代码(受此处影响)具有右键上下文菜单,当用户点击表格中的单元格时将出现。是否可以为表头中的右键单击提供不同的右键上下文菜单?如果可以,如何更改代码以实现这一点?
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()
2个回答

17

实际上比我想象中简单。与为QTableView小部件本身添加弹出菜单的方式相同,我可以从表对象获取标题,然后以与常规上下文菜单相同的方式附加上下文菜单。

headers = self.tv.horizontalHeader()
headers.setContextMenuPolicy(Qt.CustomContextMenu)
headers.customContextMenuRequested.connect(self.header_popup)

6
如果您采取继承视图而不仅是组合视图的步骤,还有另一种可能更强大的方法来完成此操作。自定义上下文菜单是否在此处起作用? 是的,但为什么除了视图之外的任何内容都需要知道它呢?这也将有助于更好地塑造您的代码以正确处理其他问题。目前的实现没有提供任何封装、内聚或支持责任分离。最终,您将拥有一个庞大的代码块,这与良好的设计背道而驰。
我提到这一点是因为您似乎将所有GUI逻辑放在这个越来越大的主函数中,这也是您将排序实现放在模型内部的原因,这对我来说毫无意义。(如果您有模型的两个视图,您会强制它们以相同的方式排序)
它是否更多的代码?是的,但它给您更多的权力,我认为值得一提。以下是演示如何处理标题和任何给定单元格的代码。此外,请注意,在我的实现中,如果存在某些其他小部件也定义了上下文菜单事件处理程序,则它们将有机会在我的后面处理该事件;因此,如果其他人仅添加了某些情况的处理程序,他们可以这样做而不会使我的代码复杂化。做到这一点的一部分是标记您是否处理了事件。
足够我胡言乱语和思考,以下是代码:
    #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

1
我很清楚它们不是必需的,也没有实际作用。但对于我和我的同事来说,我们发现这极大地提高了可读性,特别是因为我们经常在C/C++之间跳转,而这正是PEP-8所倡导的。就个人而言,我也喜欢它们,因为样式格式化工具可以以无上下文的方式纠正缩进问题,代码生成工具可以为我插入存根,并且当人们重写某些内容并更改嵌套级别时,它使合并更容易。 - UpAndAdam
好的,谢谢解释。我想这是出于习惯而不是无知,我很好奇为什么。非常感谢。 - NuclearPeon
1
我很乐意解释,但如果你好奇的话,你可以直接问问题,毕竟这是一个问答网站 :-P - UpAndAdam
虽然我本可以直接私信你,但我想其他人也可能对答案感兴趣哈哈。谢谢。 - NuclearPeon
1
我认为你误解了,从我写的方式来看,我明白为什么会这样。直接地,我只是指你在评论中的“询问”方式。你没有问一个问题;你间接地质疑了我正在做什么。将其与直接的问题“我假设你知道不需要传递;你是出于习惯还是其他原因使用它?”进行比较。更有可能被接受,让我标记你的评论为有用的,并且不让接收者感到防御。(我同意对他人有好处,但如何直接发送消息?) - UpAndAdam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接