【问题标题】:How to select an item in a QTableWidget using pytest-qt mouse click?如何使用 pytest-qt 鼠标单击在 QTableWidget 中选择一个项目?
【发布时间】:2021-08-20 02:50:37
【问题描述】:

我的主 GUI 中有一个表格。我想测试我使用右键单击项目时出现的菜单删除表中项目的能力。我正在使用 pytest-qt 进行测试。在单击小部件(例如按钮)时,使用 qtbot.mouseClick 似乎效果很好,但是当我尝试向它传递一个表格项时,它会给我一个类型错误(由于表格项不是小部件)。给我错误的代码行如下:

qtbot.mouseClick(maingui.tablename.item(row, col), Qt.RightButton)

出现错误:

TypeError: arguments did not match any overloaded call:
mouseClick(QWidget, Qt.MouseButton, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay: int = -1): argument 1 has unexpected type 'QTableWidgetItem'

鉴于文档,这个错误对我来说是有意义的。我的问题是,有没有办法做到这一点?

我认为它不应该与问题相关,但是通过右键单击表项调用的函数使用 QPoint 装饰器。我的代码对右键单击的反应如下:

@pyqtSlot(QPoint)
def on_tablename_customContextMenuRequested(self, point):
    current_cell = self.tablename.itemAt(point)
    if current_cell:
        row = current_cell.row()
        deleteAction = QAction('Delete item', self)
        editAction = QAction('Edit item', self)
        menu.addAction(deleteAction)
        menu.addAction(editAction)
        action = menu.exec_(self.tablename.mapToGlobal(point))
        if action == deleteAction:
            # <do delete stuff>
        elif action == editAction:
            # <do edit stuff>

编辑:我可以使用 eyllanesc 的建议在表格中选择一个项目,但是右键单击该项目不会显示自定义上下文菜单。这是我的问题的最小可重现示例,使用带有自定义上下文菜单的两列表。我需要能够在测试期间自动选择“删除项目”选项:

from time import sleep

import pytest
from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QTableWidgetItem, QMenu, QAction, QAbstractItemView
from tests.test_ui_generated import ui_minimum_main

pytest.main(['-s'])

class TestTable(ui_minimum_main.Ui_minimum_table, QMainWindow):
    def __init__(self, args):
        QMainWindow.__init__(self)
        self.setupUi(self)

        self.table_minimum.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table_minimum.setColumnCount(2)
        self.detectorHorizontalHeaderLabels = ['Col A', 'Col B']
        self.table_minimum.setHorizontalHeaderLabels(self.detectorHorizontalHeaderLabels)
        self.table_minimum.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.table_minimum.setSelectionBehavior(QAbstractItemView.SelectRows)

        self.table_minimum.setRowCount(1)
        self.table_minimum.setRowHeight(0, 22)
        item = QTableWidgetItem('test_col_a')
        item.setData(Qt.UserRole, 'test_col_a')
        self.table_minimum.setItem(0, 0, item)
        item = QTableWidgetItem('test_col_b')
        item.setData(Qt.UserRole, 'test_col_b')
        self.table_minimum.setItem(0, 1, item)
        self.table_minimum.resizeRowsToContents()


    @pyqtSlot(QPoint)
    def on_table_minimum_customContextMenuRequested(self, point):
        print('context_menu_requested')
        current_cell = self.table_minimum.itemAt(point)

        if current_cell:
            deleteAction = QAction('Option A- Delete Row', self)
            nothingAction = QAction('Option B- Nothing', self)
            menu = QMenu(self.table_minimum)
            menu.addAction(deleteAction)
            menu.addAction(nothingAction)
            action = self.menu.exec_(self.table_minimum.mapToGlobal(point))
            if action == deleteAction:
                self.table_minimum.setRowCount(0)
                return


def test_detector_create_delete_gui(qtbot):
    w = TestTable([])
    qtbot.addWidget(w)
    w.show()
    qtbot.waitForWindowShown(w)
    sleep(.5)

    item = w.table_minimum.item(0, 0)

    assert item is not None

    def interact_with_menu():
        # ???????
        pass

    rect = w.table_minimum.visualItemRect(item)
    QTimer.singleShot(100, interact_with_menu)
    qtbot.mouseClick(w.table_minimum.viewport(), Qt.RightButton, pos=rect.center())

【问题讨论】:

    标签: python pyqt5 pytest qtablewidget pytest-qt


    【解决方案1】:

    QTableWidgetItem 不是小部件,因此您不能直接使用它,而是必须获取与 QTableWidgetItem 关联的单元格的位置并将该信息用于鼠标单击。

    item = maingui.tablename.item(row, col)
    assert item is not None
    rect = maingui.tablename.visualItemRect(item)
    qtbot.mouseClick(maingui.tablename.viewport(), Qt.RightButton, pos=rect.center())
    

    应该注意,可能有一些单元格没有与 QTableWidgetItem 关联,所以如果你想测试这种情况,那么你必须使用 QModelIndex:

    index = maingui.tablename.model().index(row, col)
    assert index.isValid()
    rect = maingui.tablename.visualRect(index)
    qtbot.mouseClick(maingui.tablename.viewport(), Qt.RightButton, pos=rect.center())
    

    更新:

    位置是相对于 QTableWidget 的视口,所以您必须将其更改为:

    @pyqtSlot(QPoint)
    def on_table_minimum_customContextMenuRequested(self, point):
        print("context_menu_requested")
        current_cell = self.table_minimum.itemAt(point)
        if current_cell:
            deleteAction = QAction("Option A- Delete Row", self)
            nothingAction = QAction("Option B- Nothing", self)
            menu = QMenu(self.table_minimum)
            menu.addAction(deleteAction)
            menu.addAction(nothingAction)
            action = menu.exec_(self.table_minimum.viewport().mapToGlobal(point))
            if action is deleteAction:
                self.table_minimum.setRowCount(0)
                return
    

    另一方面,打开上下文菜单的事件不是单击,而是操作系统检测到您要打开上下文菜单,因此在 Qt 中,您必须通过 QContextMenuEvent 模拟该事件,如下所示:

    class Helper(QObject):
        finished = pyqtSignal()
    
    
    def test_detector_create_delete_gui(qtbot):
        helper = Helper()
    
        w = TestTable([])
        qtbot.addWidget(w)
        w.show()
        qtbot.waitForWindowShown(w)
    
        helper = Helper()
    
        def assert_row_count():
            assert w.table_minimum.rowCount() == 0
            helper.finished.emit()
    
        def handle_timeout():
            menu = None
            for tl in QApplication.topLevelWidgets():
                if isinstance(tl, QMenu):
                    menu = tl
                    break
            assert menu is not None
            delete_action = None
            for action in menu.actions():
                if action.text() == "Option A- Delete Row":
                    delete_action = action
                    break
            assert delete_action is not None
            rect = menu.actionGeometry(delete_action)
            QTimer.singleShot(100, assert_row_count)
            qtbot.mouseClick(menu, Qt.LeftButton, pos=rect.center())
    
        with qtbot.waitSignal(helper.finished, timeout=10 * 1000):
            QTimer.singleShot(1000, handle_timeout)
            item = w.table_minimum.item(0, 0)
            assert item is not None
            rect = w.table_minimum.visualItemRect(item)
            event = QContextMenuEvent(QContextMenuEvent.Mouse, rect.center())
            QApplication.postEvent(w.table_minimum.viewport(), event)
    

    【讨论】:

    • 这可以点击我表中的对象,谢谢!但是,它似乎没有打开上下文菜单。出于某种原因,该函数似乎没有采用 QPoint 信号。作为参考,我原始问题末尾的函数是主小部件类的函数。我将右键单击发送到表格小部件而不是主 gui 是否有问题,从而永远不会触发 on_tablename_customContextMenuRequested() 函数?
    • @StevenCzyz 您如何检查上下文菜单是否未打开?您可以提供minimal reproducible example 来分析错误原因。
    • 我在自定义上下文菜单函数中使用打印语句来查看是否在测试过程中调用了自定义上下文菜单。当我手动右键单击表中的一个选项时,会按预期调用打印语句。当我使用测试功能时,它没有。我已经编辑了我的原始帖子以添加一个最小可重复的示例。
    猜你喜欢
    • 2012-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多