如何让widget的PyQt事件预先运行?

问题描述 投票:0回答:1

我有一个辐射菜单的脚本,旨在当用户按下鼠标右键时出现。按住按钮时,用户可以选择“工具”。辐射菜单设计为当用户释放鼠标按钮时关闭。

但是,我的脚本遇到了问题。当我第一次按下右键单击按钮时,与辐射按钮相关的事件似乎都没有运行。为了排除故障,我决定删除按钮释放时的关闭功能。令人惊讶的是,我发现只有当我最初按下右键按钮时,我的事件才无法执行。奇怪的是,当我松开按钮时,一切都开始正常工作。我怀疑这种行为与 Qt 处理事件的方式有关。

我尝试通过实现 eventFilter 来解决此问题,但似乎没有帮助。有人可以解释一下我如何实现所需的功能吗?我希望在按下右键按钮时出现辐射菜单,允许用户选择一个“工具”,然后在释放按钮时自动关闭。

这是我迄今为止编写的脚本的示例:

import math
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *


class RadialMenu(QWidget):
    WINDOW_WIDTH = 320
    WINDOW_HEIGHT = 200

    OUTER_CIRCLE_RADIUS = 50
    OUTER_CIRCLE_DIAMETER = 2 * OUTER_CIRCLE_RADIUS

    INNER_CIRCLE_RADIUS = 10
    INNER_CIRCLE_DIAMETER = 2 * INNER_CIRCLE_RADIUS

    TEXT_WIDTH = 80
    TEXT_HEIGHT = 24
    TEXT_RADIUS = 0.5 * TEXT_HEIGHT

    NUM_ZONES = 8
    ZONE_SPAN_ANGLE = 360 / NUM_ZONES

    def __init__(self, hotkey, parent=None):
        super(RadialMenu, self).__init__(parent)
        self.setFixedSize(self.WINDOW_WIDTH, self.WINDOW_HEIGHT)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground, True)
        self.setMouseTracking(True)
        self.hotkey = hotkey
        self.tools = [None] * self.NUM_ZONES

        self.center_point = QPoint(int(0.5 * self.width()), int(0.5 * self.height()))
        self.outer_circle_rect = QRect(self.center_point.x() - self.OUTER_CIRCLE_RADIUS,
                                              self.center_point.y() - self.OUTER_CIRCLE_RADIUS,
                                              self.OUTER_CIRCLE_DIAMETER, self.OUTER_CIRCLE_DIAMETER)
        self.inner_circle_rect = QRect(self.center_point.x() - self.INNER_CIRCLE_RADIUS,
                                              self.center_point.y() - self.INNER_CIRCLE_RADIUS,
                                              self.INNER_CIRCLE_DIAMETER, self.INNER_CIRCLE_DIAMETER)

        self.selected_section = -1
        self.active_zone = -1
        self.highlight_bounds = QRect(int(self.center_point.x() - 0.5 * self.height()), 0, int(self.height()),
                                             int(self.height()))
        self.highlight_start_angle = 0

        self.hotkey_down = False
        self.mouse_moved = False

    def set_tool(self, index, label, func):
        if index < 0 or index >= self.NUM_ZONES:
            return
        self.tools[index] = (label, func)

    def distance_from_center(self, x, y):
        return math.hypot(x - self.center_point.x(), y - self.center_point.y())

    def point_from_center(self, zone, distance):
        degrees = zone * self.ZONE_SPAN_ANGLE
        radians = math.radians(degrees)

        x1 = self.center_point.x() + (distance * math.cos(radians))
        y1 = self.center_point.y() + (distance * math.sin(radians))

        return x1, y1

    def label_point(self, zone):
        distance = self.OUTER_CIRCLE_RADIUS + self.TEXT_RADIUS
        x, y = self.point_from_center(zone, distance)

        if zone in [0, 1, 7]:
            x += 2
        elif zone in [2, 6]:
            x -= 0.5 * self.TEXT_WIDTH
        elif zone in [3, 4, 5]:
            x -= 2 + self.TEXT_WIDTH

        if zone in [0, 4]:
            y -= 0.5 * self.TEXT_HEIGHT
        elif zone in [1, 3]:
            y -= 0.3 * self.TEXT_HEIGHT
        elif zone in [5, 7]:
            y -= 0.7 * self.TEXT_HEIGHT
        elif zone == 2:
            y += 2
        elif zone == 6:
            y -= 2 + self.TEXT_HEIGHT

        return x, y

    def update_active_zone(self, pos):
        if self.distance_from_center(pos.x(), pos.y()) <= self.INNER_CIRCLE_RADIUS:
            self.clear_active_zone()
            return

        point = pos - self.center_point
        degrees = math.degrees(math.atan2(point.y(), point.x())) % 360

        start_angle_offset = -0.5 * self.ZONE_SPAN_ANGLE
        degrees = (degrees + start_angle_offset) % 360

        temp_section = math.floor(degrees / self.ZONE_SPAN_ANGLE)

        if self.selected_section != temp_section:
            self.selected_section = temp_section

            self.highlight_start_angle = (self.selected_section * self.ZONE_SPAN_ANGLE) - start_angle_offset
            self.active_zone = (self.selected_section + 1) % 8

            self.update()

    def clear_active_zone(self):
        self.active_zone = -1
        self.selected_section = -1

        self.update()

    def activate_selection(self):
        if self.active_zone >= 0 and self.tools[self.active_zone]:
            self.tools[self.active_zone][1]()
        self.hide()

    def show(self):
        pop_up_pos = QCursor.pos() - self.center_point
        self.move(pop_up_pos)

        super(RadialMenu, self).show()

    def showEvent(self, show_event):
        self.setFocus(Qt.PopupFocusReason)

        self.mouse_moved = False
        self.hotkey_down = True

    def keyPressEvent(self, key_event):
        key = key_event.key()

        if key == self.hotkey and not key_event.isAutoRepeat():
            self.hide()
        elif key == Qt.Key_Escape:
            self.hide()

    def keyReleaseEvent(self, key_event):
        if key_event.key() == self.hotkey and not key_event.isAutoRepeat():
            self.hotkey_down = False

            if self.mouse_moved:
                self.activate_selection()

    def mousePressEvent(self, mouse_event):
        if self.mouse_moved or not self.hotkey_down:
            self.activate_selection()

    def mouseReleaseEvent(self, mouse_event):
        if self.mouse_moved:
            self.activate_selection()

    def mouseMoveEvent(self, mouse_event):
        print('check')
        self.update_active_zone(mouse_event.pos())

        if (mouse_event.pos() - self.center_point).manhattanLength() > self.INNER_CIRCLE_RADIUS:
            self.mouse_moved = True

    def leaveEvent(self, leave_event):
        self.clear_active_zone()

    def paintEvent(self, paint_event):
        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing)

        # Clickable area
        painter.setPen(Qt.transparent)
        painter.setBrush(QColor(0, 0, 0, 1))
        painter.drawRect(self.rect())

        # Outer Circle
        radial_grad = QRadialGradient(self.center_point, 50)
        radial_grad.setColorAt(.1, QColor(0, 0, 0, 255))
        radial_grad.setColorAt(1, QColor(0, 0, 0, 1))
        painter.setBrush(radial_grad)

        circle_pen = QPen(Qt.cyan, 2)
        painter.setPen(circle_pen)
        painter.drawEllipse(self.outer_circle_rect)

        # Zone Highlight
        if self.active_zone >= 0:
            radial_grad2 = QRadialGradient(self.center_point, 80)
            radial_grad2.setColorAt(0, QColor(255, 255, 255, 255))
            radial_grad2.setColorAt(0.9, QColor(0, 0, 0, 1))
            painter.setBrush(radial_grad2)

            painter.setPen(Qt.transparent)

            painter.drawPie(self.highlight_bounds, int(-self.highlight_start_angle * 16),
                            int(-self.ZONE_SPAN_ANGLE * 16))

        # Inner Circle
        painter.setBrush(Qt.black)
        painter.setPen(circle_pen)
        painter.drawEllipse(self.inner_circle_rect)

        for i in range(self.NUM_ZONES):
            if self.tools[i]:
                if i == self.active_zone:
                    painter.setBrush(QColor(255, 255, 0, 127))
                else:
                    painter.setBrush(QColor(0, 0, 0, 127))

                label_x, label_y = self.label_point(i)

                painter.setPen(Qt.transparent)
                painter.drawRoundedRect(int(label_x), int(label_y), int(self.TEXT_WIDTH), int(self.TEXT_HEIGHT),
                                        int(self.TEXT_RADIUS), int(self.TEXT_RADIUS))

                painter.setPen(Qt.white)
                painter.drawText(int(label_x), int(label_y), int(self.TEXT_WIDTH), int(self.TEXT_HEIGHT),
                                 Qt.AlignCenter, self.tools[i][0])

    def focusOutEvent(self, focus_event):
        self.hide()


class TestWindow(QMainWindow):
    def __init__(self, parent=None):
        super(TestWindow, self).__init__(parent=None)
        self.setWindowTitle("Radial Menu Example")
        self.setFixedSize(960, 540)
        self.setStyleSheet("background-color: #2c2f33;")
        self.radial_menus = []

        radial_menu = RadialMenu(Qt.RightButton)
        for i in range(0, RadialMenu.NUM_ZONES):
            radial_menu.set_tool(i, "Tool {0}".format(i), lambda index=i: self.activate_tool(index))
        self.radial_menus.append(radial_menu)

    def mousePressEvent(self, event):
        if event.type() == QEvent.MouseButtonPress:
            if event.button() == Qt.RightButton:
                for radial_menu in self.radial_menus:
                    if event.button() == radial_menu.hotkey:
                        radial_menu.show()

    def mouseReleaseEvent(self, event):
        if event.type() == QEvent.MouseButtonRelease:
            if event.button() == Qt.RightButton:
                for radial_menu in self.radial_menus:
                    if event.button() == radial_menu.hotkey:
                        pass
                        #radial_menu.close()

    def activate_tool(self, tool_index):
        print("Activate tool for index: {0}".format(tool_index))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = TestWindow()
    window.show()
    app.exec_()
pyqt
1个回答
0
投票

小部件只有在成为“鼠标抓取器”时才能获取鼠标移动事件,只有当它本身接收到鼠标事件时才会发生

grabMouse()
被调用。

由于合成进一步的鼠标按下并将其发布到事件队列并不能保证小部件会在实际显示后之后收到它,一种解决方案可能是在 grabMouse()

 覆盖中调用 
show()
(或
showEvent()
一个)。

请注意,一旦小部件隐藏,您

必须也调用releaseMouse()


def show(self): ... self.grabMouse() def hideEvent(self, event): self.releaseMouse()
不过,更合适的方法可能是使用 

Qt.Popup

 
window flag,它间接捕获窗口上的鼠标,类似于 QComboBox 弹出窗口的情况。请注意,可能需要在 self.update()
 内显式调用 
show()

class RadialMenu(QWidget): ... def __init__(self, hotkey, parent=None): ... self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup) def show(self): ... self.update()
    
© www.soinside.com 2019 - 2024. All rights reserved.