пятница, 1 июня 2012 г.

Контекстные меню в PyQt

Есть несколько способов обработать контекстные меню:
  1. Переопределение contextMenuEvent
  2. Использование Actions
  3. Использование CustomMenu и сигнала customContextMenuRequested


Любой из этих способов требует правильной установки проперти contextMenuPolicy.

Обработчики могут быть как в родительском объекте, так и в наследных классах.

Вариант с наследными классами не рассматриваем — не удобно возиться с дизайнером. По итогам экспериментов остановился на третьем варианте — и нормально "ловит" объект-источник, и подменю сделать можно, что в данном проектике оказалось актуально.

Для правильного позиционирования меню надо вызывать функцию mapToGlobal(pos) активного элемента, а не родительского окна!

contextMenuEvent(self, QContextMenuEvent)

  • Перехватывает обращения к контекстному меню объекта, в котором переопределён
  • Для того, чтобы перехватить контекстные меню у контролов-детей надо установить им contextMenuPolicy == QtCore.Qt.NoContextMenu.
  • Внутри обработчика получаем координаты, создаём (если надо) и показываем требуемое меню
  • Может быть использован для расширения существующего меню
  • Неправильно возвращает виджет, вызвавший срабатывание. Для QPlainTextEdit возвращается QWidget. Возможно это его viewport, но разбираться стало лень.

Actions

  • Позволяет автоматически создавать контекстное меню на основании списка actions.
  • Для того, чтобы перехватить контекстные меню у контролов-детей надо установить им contextMenuPolicy == QtCore.Qt.ActionsContextMenu.
  • Actions должны храниться в self тем или иным способом.
  • Нельзя сделать подменю
  • Нельзя расширить встроенное меню

CustomMenu

  • Перехватывает обращения к контекстному меню объекта, в котором переопределён
  • Для того, чтобы перехватить контекстные меню у контролов-детей надо установить им contextMenuPolicy == QtCore.Qt.CustomMenu.
  • Необходимо привязать обработчик ко всем контролам, где требуется переопределить поведение
  • Внутри обработчика получаем координаты, создаём (если надо) и показываем требуемое меню
  • Может быть использован для расширения существующего меню 
Пример кода из наколенного редактора ID3 тегов: окно правки текста песни с обработчиками восстановления "битой" кодировки (когда берут cp1251 и тупо обзывают iso-8859-1 — русские буквы становятся "неисправимыми" кракозбрами)

class ExpUSLTEdit(QtGui.QDialog):
    def __init__(self, parent=None):
        super(ExpUSLTEdit, self).__init__(parent)
        uic.loadUi(os.path.join(APP_PATH,'exp_uslt.ui'), self)

        # На каждый элемент привязываем обработчик контекстного меню
        self.desc.customContextMenuRequested.connect(self.onCustomMenu)
        self.text.customContextMenuRequested.connect(self.onCustomMenu)


    def onCustomMenu(self, pos):
        # Вызывается для всех нужных нам элементов

        w = self.focusWidget() # Откуда позвали

        if w:
            try:
                m = w.createStandardContextMenu() # Получаем "встроенное" меню
            except:
                return

            # Добавляем свои элементы и подменю
            m.addSeparator()
            m1 = QtGui.QMenu(self.tr("Fix encoding"))
            for s in ENCODINGS:
                a = m1.addAction(s)

            m1.triggered.connect(self.onMnu)
            m.addMenu(m1)

            a = m.exec_(w.mapToGlobal(pos))


    def onMnu(self, act):
        # Обрабатываем собственное подменю.
        # быдло-код: Текст меню используется как управляющий эелемент

        w = self.focusWidget()
        if not w: return

        try:
            # QPlainTextEdit
            if hasattr(w, "toPlainText"):
                s = (u"%s" % w.toPlainText()).encode('iso-8859-1').decode(u"%s" % act.text())
                w.setPlainText(s)
            # QLineEdit
            elif hasattr(w, "text"):
                s = (u"%s" % w.text()).encode('iso-8859-1').decode(u"%s" % act.text())
                w.setText(s)
        except Exception, e:
            QtGui.QMessageBox.warning(
                self,
                self.tr(u"Failed to change encoding"),
                u"Error: %s" % (e,)
            )

Комментариев нет:

Отправить комментарий