`pytestqt.mouseMove` 菜单栏菜单小部件移动到错误的位置
`pytestqt.mouseMove` to menubar menu widget moves to wrong place
我正在尝试将鼠标移动到菜单栏上的 File
“按钮”。在我的程序中,pytestqt.mouseMove
将鼠标移动到错误的位置(它当前在 window 标题附近单击)。
设置
OS: Windows 10 Professional x64-bit, 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
按钮,以便我可以单击它。我怎样才能做到这一点?
必须考虑以下因素:
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)
我正在尝试将鼠标移动到菜单栏上的 File
“按钮”。在我的程序中,pytestqt.mouseMove
将鼠标移动到错误的位置(它当前在 window 标题附近单击)。
设置
OS: Windows 10 Professional x64-bit, 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
按钮,以便我可以单击它。我怎样才能做到这一点?
必须考虑以下因素:
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)