Как я могу получить контекстное меню для щелчков правой кнопкой мыши в заголовках QTableView? - PullRequest
7 голосов
/ 16 октября 2011

Пример кода ниже (под сильным влиянием здесь ) имеет контекстное меню, вызываемое правой кнопкой мыши, которое будет отображаться, когда пользователь щелкает ячейки в таблице. Можно ли иметь другое контекстное меню для щелчка правой кнопкой мыши в заголовке таблицы? Если так, как я могу изменить код, чтобы включить это?

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 ]

11 голосов
/ 16 октября 2011

Оказалось проще, чем я думал. Таким же образом, как я добавляю всплывающее меню для самого виджета QTableView, я могу просто получить заголовок из объекта таблицы и затем присоединить контекстное меню таким же образом, как я делал с обычным контекстным меню.

headers = self.tv.horizontalHeader()
headers.setContextMenuPolicy(Qt.CustomContextMenu)
headers.customContextMenuRequested.connect(self.header_popup)
4 голосов
/ 09 апреля 2013

Существует другой потенциально более мощный способ сделать это, если вы сделаете шаг и унаследуете представление вместо простого его составления. Работает ли здесь пользовательское контекстное меню? Да, но почему что-то кроме представления должно знать об этом? Это также поможет лучше сформировать ваш код для правильного решения других проблем. В настоящее время реализация не обеспечивает инкапсуляцию, сплоченность или поддержку разделения ответственности. В конце у вас будет один большой шарик, который является антитезой хорошего дизайна. Я упоминаю об этом, потому что вы, кажется, помещаете всю логику графического интерфейса в эту постоянно растущую основную функцию, и именно по этой причине вы в итоге поместили реализацию sort в вашу модель, что для меня не имеет смысла. (Что, если у вас есть два вида модели, вы заставляете их сортировать одинаково)

Это больше кода? Да, но это дает вам больше силы, которую, я думаю, стоит упомянуть. Ниже я демонстрирую, как обрабатывать заголовки, а также любую ячейку, которую вы хотите. Также обратите внимание, что в моей реализации, если существует некоторый ДРУГОЙ виджет, который также определяет обработчик события контекстного меню, он потенциально может получить шанс обработать событие после моего; так что если кто-то еще добавляет обработчик только для определенных случаев, он может сделать это, не усложняя мой код. Частью этого является маркировка, если вы обработали событие или нет.

Достаточно моих бессвязных мыслей, вот код:

    #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
...