首页 > 解决方案 > `pytestqt.mouseMove` 到菜单栏菜单小部件移​​动到错误的位置

问题描述

我正在尝试将鼠标移动到File菜单栏上的“按钮”。在我的程序中,pytestqt.mouseMove将鼠标移动到错误的位置(当前正在单击窗口标题附近)。

鼠标位置错误

设置

操作系统: Windows 10 Professional x64 位,Build 1909
Python: 3.8.10 x64 位
PyQt: 5.15.4
pytest-qt: 4.0.2
IDE: VSCode 1.59.0

项目目录

gui/
├───gui/
│   │   main.py
│   │   __init__.py
│   │   
│   ├───controller/
│   │       controller.py
│   │       __init__.py
│   │
│   ├───model/
│   │      model.py
│   │       __init__.py
│   │
│   └───view/
│           view.py
│            __init__.py
├───resources/
│   │    __init__.py
│   │   
│   └───icons
│       │   main.ico
│       │   __init__.py
│       │   
│       └───toolbar
│               new.png
│               __init__.py
└───tests/
    │   conftest.py
    │   __init__.py
    │
    └───unit_tests
            test_view.py
            __init__.py

代码

gui/main.py

from PyQt5.QtWidgets import QApplication

from gui.controller.controller import Controller
from gui.model.model import Model
from gui.view.view import View


class MainApp:
    def __init__(self) -> None:
        self.controller = Controller()
        self.model = self.controller.model
        self.view = self.controller.view

    def show(self) -> None:
        self.view.showMaximized()


if __name__ == "__main__":
    app = QApplication([])
    root = MainApp()
    root.show()
    app.exec_()

gui/view.py

from typing import Any

from PyQt5.QtCore import QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QFrame, QGridLayout, QStatusBar, QToolBar, QWidget
from pyvistaqt import MainWindow

from resources.icons import toolbar


class View(MainWindow):
    def __init__(
        self, controller, parent: QWidget = None, *args: Any, **kwargs: Any
    ) -> None:
        super().__init__(parent, *args, **kwargs)
        self.controller = controller

        # Set the window name
        self.setWindowTitle("GUI Demo")

        # Create the container frame
        self.container = QFrame()

        # Create the layout
        self.layout = QGridLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)

        # Set the layout
        self.container.setLayout(self.layout)
        self.setCentralWidget(self.container)

        # Create and position widgets
        self._create_actions()
        self._create_menubar()
        self._create_toolbar()
        self._create_statusbar()

    def _create_actions(self):
        self.new_icon = QIcon(toolbar.NEW_ICO)

        self.new_action = QAction(self.new_icon, "&New Project...", self)
        self.new_action.setStatusTip("Create a new project...")

    def _create_menubar(self):
        self.menubar = self.menuBar()

        self.file_menu = self.menubar.addMenu("&File")

        self.file_menu.addAction(self.new_action)

    def _create_toolbar(self):
        self.toolbar = QToolBar("Main Toolbar")
        self.toolbar.setIconSize(QSize(16, 16))

        self.addToolBar(self.toolbar)

        self.toolbar.addAction(self.new_action)

    def _create_statusbar(self):
        self.statusbar = QStatusBar(self)
        self.setStatusBar(self.statusbar)

gui/model.py

from typing import Any


class Model(object):
    def __init__(self, controller, *args: Any, **kwargs: Any):
        self.controller = controller

gui/controller.py

from typing import Any

from gui.model.model import Model
from gui.view.view import View


class Controller(object):
    def __init__(self, *args: Any, **kwargs: Any):
        self.model = Model(controller=self, *args, **kwargs)
        self.view = View(controller=self, *args, **kwargs)

resources/icons/toolbar/__init__.py

import importlib.resources as rsrc

from resources.icons import toolbar

with rsrc.path(toolbar, "__init__.py") as path:
    NEW_ICO = str((path.parent / "new.png").resolve())

test/conftest.py

from typing import Any, Callable, Generator, List, Sequence, Union

import pytest
import pytestqt
from pytestqt.qtbot import QtBot
from gui.main import MainApp
from PyQt5 import QtCore

pytest_plugins: Union[str, Sequence[str]] = ["pytestqt.qtbot",]
"""A ``pytest`` global variable that registers plugins for use in testing."""


@pytest.fixture(autouse=True)
def clear_settings() -> Generator[None, None, None]:
    yield
    QtCore.QSettings().clear()


@pytest.fixture
def app(qtbot: QtBot) -> Generator[MainApp, None, None]:
    # Setup
    root = MainApp()
    root.show()
    qtbot.addWidget(root.view)

    # Run
    yield root

    # Teardown - None

test/unit_tests/test_view.py

import time

from PyQt5 import QtCore, QtWidgets
import pytest
from pytestqt import qt_compat
from pytestqt.qt_compat import qt_api
from pytestqt.qtbot import QtBot

from gui.main import MainApp


def test_menubar_click(app: MainApp, qtbot: QtBot) -> None:
    # Arrange
    file_menu = app.view.file_menu
    file_menu.setMouseTracking(True)

    qtbot.addWidget(file_menu)

    # Act
    qtbot.wait(1000)
    qtbot.mouseMove(file_menu)
    qtbot.wait(5000)
    
    # Assert
    assert False

问题:

鼠标移动到错误的地方:

鼠标位置错误

我需要鼠标移动到File按钮,以便我可以单击它。我怎样才能做到这一点?

标签: pythonpyqt5pytestpytest-qt

解决方案


必须考虑以下事项:

  • QMenu 不是“文件按钮”,而是按下该元素时显示的弹出窗口。由于这个原因,由于它不可见,屏幕的左上角被作为参考(因为它没有父级)和 QMenu 的建议大小。

  • 那个“文件按钮”也不是 QWidget,而是 QMenuBar 的一部分,其中与 QMenu(menuAction()方法) 关联的 QAction 用于绘制它,因此 mouseMove 必须使用QMenuBaractionGeometry()方法来获取项目的坐标。

def test_menubar_click(app: MainApp, qtbot: QtBot) -> None:
    # Arrange
    file_menu = app.view.file_menu
    menubar = app.view.menubar
    qtbot.add_widget(menubar)
    # Act
    action_rect = menubar.actionGeometry(file_menu.menuAction())
    qtbot.wait(3000)
    qtbot.mouseMove(menubar, action_rect.center())
    qtbot.wait(3000)

推荐阅读