如何在 QTableView 标题中单击鼠标右键单击上下文菜单?

发布于 2024-12-10 03:36:34 字数 3716 浏览 0 评论 0原文

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

挽心 2024-12-17 03:36:34

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

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

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)
苏佲洛 2024-12-17 03:36:34

如果您采取步骤并继承视图而不是简单地组合视图,那么还有另一种可能更强大的方法可以实现此目的。自定义上下文菜单在这里起作用吗?是的,但是为什么除了视图之外还需要了解它呢?它还将有助于更好地塑造您的代码以正确处理其他问题。目前,该实现不提供任何封装、内聚或支持责任分离。最终你会得到一大团东西,这与优秀设计是对立的。
我提到这一点是因为您似乎将所有 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
        elif <some other condition>:
            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

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
        elif <some other condition>:
            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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文