`pytestqt.mouseMove` в виджет меню строки меню перемещается в неправильное место

avatar
A. Hendry
8 августа 2021 в 22:25
109
1
0

Я пытаюсь подвести мышь к "кнопке" File в строке меню. В моей программе pytestqt.mouseMove перемещает мышь не в то место (в настоящее время она щелкает рядом с заголовком окна).

Mouse Wrong Position

Настройка

OS: Windows 10 Professional x64-bit, Build 1909
Python: 3.8.10 x64-bit
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

Проблема:

Мышь перемещается не в то место:

Mouse Wrong Position

Мне нужно, чтобы мышь переместилась к кнопке File, чтобы я мог щелкнуть ее. Как мне этого добиться?

Источник

Ответы (1)

avatar
eyllanesc
8 августа 2021 в 23:37
3

Необходимо учитывать следующее:

  • QMenu — это не кнопка «Файл», а всплывающее окно, которое отображается при нажатии на этот элемент. По этой причине, поскольку он не виден, верхний левый угол экрана берется в качестве ссылки (поскольку у него нет родителя) и предлагается размер QMenu .

  • Эта кнопка «Файл» также не является QWidget, а является частью QMenuBar, где QAction, связанный с методом QMenu(menuAction()), используется для ее рисования, поэтому mouseMove должен использовать QMenuBar и actionGeometry() метод для получения координат элемента.

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)
A. Hendry
8 августа 2021 в 23:48
0

Блестяще еще раз! У вас есть книга или ресурс, который вы можете порекомендовать для лучшего изучения Qt в Python? Я читал «Создание приложений с графическим интерфейсом с помощью Python и Qt 5» Мартина Фитцпатрика, «Современный PyQt» Джошуа Уильямса и «Начало PyQt» Джошуа М. Уиллмана, но ни в одном из них нет подробной информации. Похоже, вы очень хорошо разбираетесь в Qt. Вы случайно не написали книгу?

eyllanesc
9 августа 2021 в 00:13
1

@A.Hendry Вам просто нужно прочитать документы Qt, там четко указано, что представляет собой каждый элемент и как они взаимодействуют.

A. Hendry
9 августа 2021 в 00:50
0

Дополняющий вопрос. Я пытаюсь прочитать документы, чтобы понять, как имитировать щелчок мыши, открывающий меню. qtbot.mouseClick(menubar, qt_api.QtCore.Qt.LeftButton) не работает. Как мне это сделать?

eyllanesc
9 августа 2021 в 01:17
1

@A.Hendry Я думаю, вы знаете, что если вы передадите позицию, то будет использоваться центр виджета (в данном случае строка меню), с другой стороны, QMenu не отображается мгновенно, поэтому дайте ему задержку: qtbot.mouseClick(menubar, QtCore.Qt.LeftButton, pos=rect.center()) qtbot.wait(3000)

A. Hendry
9 августа 2021 в 01:19
0

@eyllansc facepalm! Я забыл аргумент позиции! Спасибо!! Вы супер полезны!

A. Hendry
9 августа 2021 в 01:24
0

Извините, последний вопрос. Я знаю, что отнимаю у тебя много времени. Как мне теперь (1) перейти к пункту меню «Файл», который появляется под «кнопкой «Файл», и (2) щелкнуть по нему? Я куплю тебе еще две чашки кофе :)

eyllanesc
9 августа 2021 в 01:36
0

@ A.Hendry Вы хотите нажать «Новый проект ...»?

A. Hendry
9 августа 2021 в 01:43
0

Да сэр. Я хочу нажать «Новый проект...» и убедиться, что он делает то, что должен.

eyllanesc
9 августа 2021 в 01:45
1

@A.Hendry Ниже приведен код: gist.github.com/eyllanesc/ded349044bf43dd79f8c43acb049b263, я удалю его через 1 или 2 дня.

A. Hendry
9 августа 2021 в 01:48
0

@eyllansc А, это имеет смысл. Просто промойте и повторите тот же код, что и для строки меню. Спасибо!

A. Hendry
9 августа 2021 в 01:53
0

Не могли бы вы добавить суть ответа, чтобы другие тоже могли извлечь из этого пользу?

eyllanesc
9 августа 2021 в 01:56
0

@ A.Hendry Нет, так как это не вопрос вашего поста.

A. Hendry
9 августа 2021 в 01:58
0

Понял и не переживай. Еще раз спасибо за вашу помощь!

A. Hendry
9 августа 2021 в 02:00
0

@eyllansc Поскольку вы уже столкнулись с проблемой, хотите ли вы, чтобы я поместил вопросы в этих комментариях как отдельный вопрос SO, чтобы я мог дать вам баллы за ответ?

eyllanesc
9 августа 2021 в 02:02
0

@ A.Hendry Нет, если у вас есть время, вы публикуете вопрос и публикуете ответ. Баллы банальные и с моей репутацией меньше.