【问题标题】:How can I get right-click context menus for clicks in QTableView header?如何在 QTableView 标题中获得右键单击上下文菜单?
【发布时间】:2011-12-08 14:02:50
【问题描述】:

下面的示例代码(深受here 的影响)有一个右键单击上下文菜单,当用户单击表格中的单元格时会出现该菜单。是否可以在表格标题中为右键单击设置不同的右键单击上下文菜单?如果是这样,我该如何更改代码以合并它?

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

【问题讨论】:

    标签: python header pyqt contextmenu qtableview


    【解决方案1】:

    如果您采取步骤并继承视图而不是简单地组合它,那么还有另一种可能更强大的方法来执行此操作。自定义上下文菜单在这里工作吗?是的,但是为什么除了视图之外的任何东西都需要知道呢?它还有助于更好地塑造您的代码以正确处理其他问题。目前该实现不提供任何封装、内聚或支持责任分离。最后你会得到一个大斑点,它是好的设计的对立面。 我提到这一点是因为您似乎将所有 GUI 逻辑都放在了这个不断增长的主函数中,这也是您最终将排序实现放在模型中的原因,这对我来说毫无意义。 (如果你有两个模型视图,你会强制它们以相同的方式排序)

    是不是更多的代码?是的,但它给了你更多的力量,我认为这是值得一提的。下面我将演示如何处理标题以及您想要的任何给定单元格。另请注意,在我的实现中,如果存在一些还定义了上下文菜单事件处理程序的 OTHER 小部件,它可能有机会在处理我之后的事件时遇到问题;因此,如果其他人只为某些情况添加处理程序,他们可以这样做而不会使我的代码复杂化。这样做的一部分是标记您是否处理了事件。

    我的胡言乱语和想法已经够多了,下面是代码:

        #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
    

    【讨论】:

    • 我很清楚它们不是必需的并且是无操作的。对我(以及与我一起工作的人)来说,我们发现它极大地提高了可读性,特别是因为我们在 C/C++ 中来回跳跃,而这正是 PEP-8 所提倡的。就我个人而言,我也喜欢它们,因为样式格式化工具可以以与上下文无关的方式纠正缩进问题,代码生成工具可以为我插入具有有限末端的存根,并且当人们重写更改嵌套级别的内容时,它使合并更容易。
    • 好的,谢谢您的解释。我认为这是出于习惯而不是无知,我很想知道为什么。非常感谢。
    • 总是乐于解释,但如果你好奇,你可以直接问你的问题,这是一个问答网站:-P
    • 虽然我可以直接给你发消息,但我想其他人可能也有兴趣知道答案,哈哈。谢谢
    • 我认为你误解了,我从我写它的方式中明白了为什么。我直接指的是您在评论中“询问”的方式。你没有问问题;你发表声明间接质疑我在做什么什么。将此与直接问题进行比较“我假设您知道不需要通过;您是出于习惯还是出于其他原因使用它?”更有可能是:很受欢迎,因为我将您的评论标记为有用,而不是让收件人感到防御。 (我同意对别人好,你怎么直接留言?)
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-27
      • 1970-01-01
      • 2016-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多