`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 必须使用 QMenuBar
和actionGeometry()
方法获取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` 到菜单栏菜单小部件移动到错误的位置的主要内容,如果未能解决你的问题,请参考以下文章