`pytestqt.mouseMove` 到菜单栏菜单小部件移​​动到错误的位置

Posted

技术标签:

【中文标题】`pytestqt.mouseMove` 到菜单栏菜单小部件移​​动到错误的位置【英文标题】:`pytestqt.mouseMove` to menubar menu widget moves to wrong place 【发布时间】:2021-08-08 22:25:15 【问题描述】:

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

设置

操作系统: Windows 10 Professional x64 位,Build 1909Python: 3.8.10 x64 位PyQt: 5.15.4pytest-qt: 4.0.2IDE: 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 按钮,这样我才能单击它。我怎样才能做到这一点?

【问题讨论】:

【参考方案1】:

必须考虑以下几点:

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

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

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)

【讨论】:

再次辉煌!你有没有可以推荐的书或资源来更好地学习 Python 中的 Qt?我读过 Martin Fitzpatrick 的“使用 Python 和 Qt 5 创建 GUI 应用程序”、Joshua Williams 的“Modern PyQt”和 Joshua M. Willman 的“Beginning PyQt”,但这些都没有详细的信息。您似乎对 Qt 掌握得很好。你有没有偶然写过一本书? @A.Hendry 你只需要阅读 Qt 文档,它清楚地说明了每个元素是什么以及它们如何交互。 后续问题。我正在尝试阅读文档以了解如何模拟打开菜单的鼠标单击。 qtbot.mouseClick(menubar, qt_api.QtCore.Qt.LeftButton) 不起作用。我该怎么做? @A.Hendry 我想你知道如果你传递了位置,那么小部件的中心将被使用(在这种情况下是菜单栏),另一方面 QMenu 不会立即显示所以给它一个延迟:qtbot.mouseClick(menubar, QtCore.Qt.LeftButton, pos=rect.center())qtbot.wait(3000) @A.Hendry 下面的要点是代码:gist.github.com/eyllanesc/ded349044bf43dd79f8c43acb049b263,我会在一两天内删除它

以上是关于`pytestqt.mouseMove` 到菜单栏菜单小部件移​​动到错误的位置的主要内容,如果未能解决你的问题,请参考以下文章

Windows10隐藏开始菜单栏,改变菜单栏位置

将菜单添加到加载项中的 Visual Studio 菜单栏

将菜单栏添加到 JFrame

Delphi -- 创建 桌面发送到...快速启动栏开始菜单程序菜单右键菜 单

如何在 uwp 中将图标添加到菜单栏

DELPHI标题栏添加菜单栏