在 PyQt5 中,如何使用拖放正确移动 QTableView 中的行

2024-06-22

我(只是)希望能够使用QTableView用于移动现有行的拖放机制。我找到了很多来源(例如here https://stackoverflow.com/questions/12168610/qtableview-drag-move-rows, here https://forum.qt.io/topic/63799/qtableview-drag-and-drop or here https://www.qtcentre.org/threads/67260-QTableView-move-internal-is-very-confusing) 描述了some拖、放、插入等方面,但我仍在努力使其适合我的情况。

这是我正在寻找的解决方案应该具备的功能:

  • 研究“无 Qt”数据结构,例如元组列表。
  • 对数据结构进行操作。即当项目的顺序得到时 在视图中修改应该在数据结构中修改
  • look and feel of standard drag&drop enabled lists:
    • 选择/移动整行
    • 显示整条线的下降指示器
  • 诸如删除/编辑单元格之类的进一步操作仍然是可能的 即不被拖放方法所触及

本教程 http://apocalyptech.com/linux/qt/qtableview/显示了一个解决方案very接近我需要的,但它使用QStandardItemModel而不是QAbstractTableModel这对我来说看起来是半优化的,因为我必须在基于的“镜像”数据结构上进行操作QStandardItem这是需要的QStandardItemModel(我对吗?)

下面附加了代表我当前进度的代码。

目前我看到两种可能的方法:

方法一:实施反对QAbstractTableModel并实现所有需要的事件/槽来修改底层数据结构: * pro:最通用的方法 * pro:没有冗余数据 * 缺点:我不知道如何获知已完成的拖放操作 操作以及哪些索引移动到了哪里

在我附加的代码中,我跟踪了我所知道的所有相关方法并打印出所有参数。这是将第 2 行拖到第 3 行时得到的结果

dropMimeData(data: ['application/x-qabstractitemmodeldatalist'], action: 2, row: -1, col: -1, parent: '(row: 2, column: 0, valid: True)')
insertRows(row=-1, count=1, parent=(row: 2, column: 0, valid: True))
setData(index=(row: 0, column: 0, valid: True), value='^line1', role=0)
setData(index=(row: 0, column: 1, valid: True), value=1, role=0)
removeRows(row=1, count=1, parent=(row: -1, column: -1, valid: False))

此输出向我提出了以下问题:

  • why do moveRow/moveRows没有接到电话?他们什么时候会被召唤?
  • why are insertRow/removeRow不被调用,但仅insertRows/removeRows?
  • 行索引是什么-1 mean?
  • 我可以用中提供的 mime 数据做什么dropMimeData?我以后应该用它来复制数据吗?

方法2: Use QStandardItemModel并与管理的数据并行修改您的数据QStandardItemModel。 * 专业人士:有一个工作示例 http://apocalyptech.com/linux/qt/qtableview/* 相反:你管理一个必须一致的冗余数据结构 与另一个内部管理的数据结构。 * 相反:也不知道如何做到这一点

这是我目前使用的方法QAbstractTableModel:

from PyQt5 import QtWidgets, QtCore, QtGui

class MyModel(QtCore.QAbstractTableModel):
    def __init__(self, data, parent=None, *args):
        super().__init__(parent, *args)
        self._data = data

    def columnCount(self, parent):
        return 2

    def rowCount(self, parent):
        return len(self._data)

    def headerData(self, column: int, orientation, role: QtCore.Qt.ItemDataRole):
        return (('Regex', 'Category')[column] 
                if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal
                else None)

    def data(self, index, role: QtCore.Qt.ItemDataRole):
        if role not in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole}:
            return None

        print("data(index=%s, role=%r)" % (self._index2str(index), self._role2str(role)))
        return (self._data[index.row()][index.column()] 
               if index.isValid()
               and role in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole} 
               and index.row() < len(self._data)
               else None)

    def setData(self, index: QtCore.QModelIndex, value, role: QtCore.Qt.ItemDataRole):

        print("setData(index=%s, value=%r, role=%r)" % (self._index2str(index), value, role))
        return super().setData(index, value, role)

    def flags(self, index):
        return (
           super().flags(index) 
            | QtCore.Qt.ItemIsDropEnabled
            | (QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled)
              if index.isValid() else QtCore.Qt.NoItemFlags)

    def dropMimeData(self, data, action, row, col, parent: QtCore.QModelIndex):
        """Always move the entire row, and don't allow column 'shifting'"""
        print("dropMimeData(data: %r, action: %r, row: %r, col: %r, parent: %r)" % (
            data.formats(), action, row, col, self._index2str(parent)))
        assert action == QtCore.Qt.MoveAction
        return super().dropMimeData(data, action, row, 0, parent)

    def supportedDragActions(self):
        return QtCore.Qt.MoveAction

    def supportedDropActions(self):
        return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction

    def removeRow(self, row: int, parent=None):
        print("removeRow(row=%r):" % (row))
        return super().removeRow(row, parent)

    def removeRows(self, row: int, count: int, parent=None):
        print("removeRows(row=%r, count=%r, parent=%s)" % (row, count, self._index2str(parent)))
        return super().removeRows(row, count, parent)

    def insertRow(self, index, parent=None):
        print("insertRow(row=%r, count=%r):" % (row, count))
        return super().insertRow(row, count, parent)

    def insertRows(self, row: int, count: int, parent: QtCore.QModelIndex = None):
        print("insertRows(row=%r, count=%r, parent=%s)" % (row, count, self._index2str(parent)))
        return super().insertRows(row, count, parent)

    @staticmethod
    def _index2str(index):
        return "(row: %d, column: %d, valid: %r)" % (index.row(), index.column(), index.isValid())

    @staticmethod
    def _role2str(role: QtCore.Qt.ItemDataRole) -> str:
        return "%s (%d)" % ({
            QtCore.Qt.DisplayRole: "DisplayRole",
            QtCore.Qt.DecorationRole: "DecorationRole",
            QtCore.Qt.EditRole: "EditRole",
            QtCore.Qt.ToolTipRole: "ToolTipRole",
            QtCore.Qt.StatusTipRole: "StatusTipRole",
            QtCore.Qt.WhatsThisRole: "WhatsThisRole",
            QtCore.Qt.SizeHintRole: "SizeHintRole",

            QtCore.Qt.FontRole: "FontRole",
            QtCore.Qt.TextAlignmentRole: "TextAlignmentRole",
            QtCore.Qt.BackgroundRole: "BackgroundRole",
            #QtCore.Qt.BackgroundColorRole:
            QtCore.Qt.ForegroundRole: "ForegroundRole",
            #QtCore.Qt.TextColorRole
            QtCore.Qt.CheckStateRole: "CheckStateRole",
            QtCore.Qt.InitialSortOrderRole: "InitialSortOrderRole",
        }[role], role)


class MyTableView(QtWidgets.QTableView):
    class DropmarkerStyle(QtWidgets.QProxyStyle):
        def drawPrimitive(self, element, option, painter, widget=None):
            """Draw a line across the entire row rather than just the column we're hovering over.
            This may not always work depending on global style - for instance I think it won't
            work on OSX."""
            if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
                option_new = QtWidgets.QStyleOption(option)
                option_new.rect.setLeft(0)
                if widget:
                    option_new.rect.setRight(widget.width())
                option = option_new
            super().drawPrimitive(element, option, painter, widget)

    def __init__(self):
        super().__init__()
        self.setStyle(self.DropmarkerStyle())
        # only allow rows to be selected
        self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        # disallow multiple rows to be selected
        self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
        self.setDragEnabled(True)

        self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
        self.setDropIndicatorShown(True) # default
        self.setAcceptDrops(False)           # ?
        self.viewport().setAcceptDrops(True) # ?
        self.setDragDropOverwriteMode(False)


class HelloWindow(QtWidgets.QMainWindow):
    def __init__(self) -> None:
        super().__init__()

        model = MyModel([("^line0", 0),
                         ("^line1", 1),
                         ("^line2", 2),
                         ("^line3", 3)])

        table_view = MyTableView()
        table_view.setModel(model)
        table_view.verticalHeader().hide()
        table_view.setShowGrid(False)

        self.setCentralWidget(table_view)


def main():
    app = QtWidgets.QApplication([])
    window = HelloWindow()
    window.show()
    app.exec_()

if __name__ == "__main__":
    main()

我还不知道如何制作QAbstractTableModel or QAbstractItemModel按照描述的方式工作,但我终于找到了一种方法QTableView处理拖放并使模型移动一行。

这是代码:

from PyQt5 import QtWidgets, QtCore

class ReorderTableModel(QtCore.QAbstractTableModel):
    def __init__(self, data, parent=None, *args):
        super().__init__(parent, *args)
        self._data = data

    def columnCount(self, parent=None) -> int:
        return 2

    def rowCount(self, parent=None) -> int:
        return len(self._data) + 1

    def headerData(self, column: int, orientation, role: QtCore.Qt.ItemDataRole):
        return (('Regex', 'Category')[column]
                if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal
                else None)

    def data(self, index: QtCore.QModelIndex, role: QtCore.Qt.ItemDataRole):
        if not index.isValid() or role not in {QtCore.Qt.DisplayRole, QtCore.Qt.EditRole}:
            return None
        return (self._data[index.row()][index.column()] if index.row() < len(self._data) else
                "edit me" if role == QtCore.Qt.DisplayRole else "")

    def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
        # https://doc.qt.io/qt-5/qt.html#ItemFlag-enum
        if not index.isValid():
            return QtCore.Qt.ItemIsDropEnabled
        if index.row() < len(self._data):
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable

    def supportedDropActions(self) -> bool:
        return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction

    def relocateRow(self, row_source, row_target) -> None:
        row_a, row_b = max(row_source, row_target), min(row_source, row_target)
        self.beginMoveRows(QtCore.QModelIndex(), row_a, row_a, QtCore.QModelIndex(), row_b)
        self._data.insert(row_target, self._data.pop(row_source))
        self.endMoveRows()


class ReorderTableView(QtWidgets.QTableView):
    """QTableView with the ability to make the model move a row with drag & drop"""

    class DropmarkerStyle(QtWidgets.QProxyStyle):
        def drawPrimitive(self, element, option, painter, widget=None):
            """Draw a line across the entire row rather than just the column we're hovering over.
            This may not always work depending on global style - for instance I think it won't
            work on OSX."""
            if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
                option_new = QtWidgets.QStyleOption(option)
                option_new.rect.setLeft(0)
                if widget:
                    option_new.rect.setRight(widget.width())
                option = option_new
            super().drawPrimitive(element, option, painter, widget)

    def __init__(self, parent):
        super().__init__(parent)
        self.verticalHeader().hide()
        self.setSelectionBehavior(self.SelectRows)
        self.setSelectionMode(self.SingleSelection)
        self.setDragDropMode(self.InternalMove)
        self.setDragDropOverwriteMode(False)
        self.setStyle(self.DropmarkerStyle())

    def dropEvent(self, event):
        if (event.source() is not self or
            (event.dropAction() != QtCore.Qt.MoveAction and
             self.dragDropMode() != QtWidgets.QAbstractItemView.InternalMove)):
            super().dropEvent(event)

        selection = self.selectedIndexes()
        from_index = selection[0].row() if selection else -1
        to_index = self.indexAt(event.pos()).row()
        if (0 <= from_index < self.model().rowCount() and
            0 <= to_index < self.model().rowCount() and
            from_index != to_index):
            self.model().relocateRow(from_index, to_index)
            event.accept()
        super().dropEvent(event)


class Testing(QtWidgets.QMainWindow):
    """Demonstrate ReorderTableView"""
    def __init__(self):
        super().__init__()
        view = ReorderTableView(self)
        view.setModel(ReorderTableModel([
            ("a", 1),
            ("b", 2),
            ("c", 3),
            ("d", 4),
        ]))
        self.setCentralWidget(view)

        self.show()


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    test = Testing()
    raise SystemExit(app.exec_())
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 PyQt5 中,如何使用拖放正确移动 QTableView 中的行 的相关文章

  • 将黑白图像完全转换为一组线条(也称为仅使用线条进行矢量化)

    我有许多黑白图像 想将它们转换为一组线条 这样我就可以完全或至少接近完全地从线条重建原始图像 换句话说 我试图将图像矢量化为一组线条 我已经看过了霍夫线变换 https docs opencv org2 4 modules imgproc
  • 在 Pandas UDF PySpark 中传递多列

    我想计算 PySpark DataFrame 两列之间的 Jaro Winkler 距离 Jaro Winkler 距离可通过所有节点上的 pyjarowinkler 包获得 pyjarowinkler 的工作原理如下 from pyjar
  • Python 中的 Hello World [重复]

    这个问题在这里已经有答案了 我尝试运行一个 python 脚本 print Hello World 我收到此错误 File hello py line 1 print Hello World SyntaxError invalid synt
  • Travis-ci 和 Gobject 内省

    我正在尝试设置 Travis获取 GNOME https github com getting things gnome gtg My travis yml https github com getting things gnome gtg
  • 导入父目录进行简单测试

    我从上到下搜索了这个网站 但还没有找到一种方法来真正实现我在 Python3x 中想要的东西 这是一个简单的玩具应用程序 所以我想我可以在断言中编写一些简单的测试用例 然后就到此为止了 它确实会生成报告等 因此我想确保我的代码在更改时不会做
  • 如何设置appache2的WSGI与python 3.7一起使用?

    我使用的是 ubuntu 16 04 并安装了 python 3 7 并使用以下说明将其设置为默认值 无法在 ubuntu 中将默认 python 版本设置为 python3 https stackoverflow com question
  • 如何拖放 LibGDX Image actor

    我有一个 LibGDX 场景 其中有几个图像 Actor 子类 我想将一个图像拖放到另一个图像上 我从位于的源代码开始DragDropTest java https github com libgdx libgdx blob master
  • 使用字典来键入一系列值[重复]

    这个问题在这里已经有答案了 我有一个 pandas 数据框 我想根据另一列的值在新列中创建类别 我可以通过这样做来解决我的基本问题 range range 0 5 Below 5 range 6 10 between range 11 10
  • 在 MACOSX 上找不到“Python.h”文件,如何解决这个问题?

    pip3 安装 PyAudio 0 2 12默认为用户安装 因为普通站点包不可写 收集PyAudio 0 2 12 使用缓存的 PyAudio 0 2 12 tar gz 42 kB 安装构建依赖项 完成 获取建造轮子的要求 完成 准备元数
  • Qt 模型/视图与标准小部件

    我目前正在阅读模型 视图tutorial http qt project org doc qt 4 8 modelview html来自 Qt 但我仍然不确定是否应该为我的 Qt 程序使用模型 视图或小部件 我需要做一个记录器应用程序来监视
  • Python Camelot无边框表格提取问题

    我正在努力从 pdf 文件中提取一些无边框表格 如下图所示 我已经安装了 python camelot 如图所示here https github com socialcopsdev camelot并且仅适用于有边框的表格 请参阅以下详细信
  • 在两个片段之间拖放视图

    我目前正在尝试在两个片段之间实现拖放 我已经将它们添加到我的活动中 如下所示 FragmentManager fm getFragmentManager FragmentTransaction ft fm beginTransaction
  • django - 不支持对 JSONField 的查找或不允许在该字段上加入

    我的模型中有一个 Json 字段 class Product models Model detailed stock JSONField load kwargs object pairs hook collections OrderedDi
  • 如何使用 pip 安装 Openpyxl

    我有 Windows 10 64 位 我想利用Openpyxl包开始学习如何与 Excel 和其他电子表格交互 我安装了Python windowsx86 64web basedinstaller 我有 64 位操作系统 尝试安装此版本时我
  • 为什么安装成功后无法导入pandas?

    我已经使用命令 pip3 4 install pandas 安装了 pandas Successfully installed pandas python dateutil pytz numpy six Cleaning up root h
  • FlowLayoutPanels 中的 C# 拖放标签

    我有一个小问题 我想要制作一个程序 可以在多个 FlowLayoutPanel 之间拖动生成的标签 但最近几天我尝试让拖放工作 我尝试了很多教程 示例等 但总是有些不同 而且我无法仅提取基本代码 它类似于这个程序 https social
  • 装饰器更改返回类型时键入函数

    如何正确编写返回类型被装饰器修改的函数的类型 简单的例子 def example decorator fn def wrapper data res fn data return join res return wrapper exampl
  • 当遵循文档代码时,Python 多处理返回 AttributeError [重复]

    这个问题在这里已经有答案了 我决定尝试使用多处理器模块来帮助加速我的程序 为了弄清楚这一点 我尝试使用有关多处理的官方 python 文档中的一些代码示例 第一次尝试 介绍 https docs python org 3 library m
  • QAbstractListModel 中的 PyQt QWidget 被 QSortFilterProxyModel 删除

    我需要用小部件填充列表视图 然后让自定义代理过滤器使用它 如果没有过滤器 它的效果很好 当激活时 它似乎会删除附加到模型的小部件 它显示所有项目 过滤工作正常 但是当删除过滤器时 当隐藏的小部件应该再次显示时 会抛出错误 custom wi
  • python3-numpy:使用 numpy savetxt 附加到文件

    我正在尝试使用 numpy 的 savetxt 函数将数据附加到文件中 下面是最小的工作示例 usr bin env python3 import numpy as np f open asd dat a for iind in range

随机推荐

  • ASP.net MVC 中的 ASP.net AJAX 有什么意义?

    因此 ASP net MVC 默认情况下附带 JQuery 和 ASP net Ajax 虽然 JQuery 的用例很明显而且很多 但我只是想知道 ASP net Ajax 的意义是什么 如果我只是让我的控制器操作返回 JSON 我不需要它
  • “git”无法可见,因为它的所有子项都位于不可用的操作集中

    我有这个问题 我不知道如何获取工具栏上的 Git 部分 下图显示了具体的错误 感谢您的所有回答 在 操作集可用性 中勾选 Git 和 Git 导航操作 然后在 工具栏可见性 中勾选 Git
  • C++:使用声明和重载范例

    我在看这一页 https www cppstories com 2018 09 visit variants 关于 C 17 的 新 功能 特别是我理解几乎所有以下代码 include
  • Android 完成活动不起作用

    一旦用户从我的 ListView 中选择了产品 它就会将该 ListView 中选定的文本放入 EditText 中 我遇到的问题是 当用户从列表中选择一个产品 然后按返回时 它会再次显示该列表 而不是返回到 EditText 活动 我尝试
  • 为什么 Android Studio 无法识别 .jar 库导入?

    我正在尝试在我的 Android Studio 项目中使用一些 jar 文件作为库 我一直在寻找如何做到这一点 常规方法是将 jar 文件复制到 libs 文件夹中 然后将其添加为库 我知道 libs 文件夹必须位于 项目 视图中的 app
  • 切换到heroku cedar-14导致内存消耗持续增加

    Heroku 最近宣布今年 11 月之后将不再支持 cedar 10 切换到 cedar 14 导致内存消耗增加 直到我遇到 R14 内存配额超出 错误并不得不重新启动 heroku 在我开始使用 unicorn worker killer
  • 使用 Javascript 将变量传递给弹出窗口

    我需要将一些文本从当前页面传递到弹出窗口 而不需要服务器点击 该信息 此处用 90 表示 已在父表单中可用 它就像存储在隐藏变量中的一段长文本 我只需要将其显示为弹出窗口 这是我尝试过的 这在某种程度上有效 但如果我传递文本而不是数字 则不
  • Java 错误“未使用局部变量的值”

    我对java真的很陌生 2天前开始学习 抱歉 如果这是一个愚蠢的问题 我正在尝试学习如何使用 rt exec 和类似的方法 因此我尝试制作一个运行 calc exe 的非常简单的程序 这是代码 public class main try R
  • 如何重新启动模拟器进入恢复模式

    有什么办法可以将模拟器重新启动到恢复模式吗 Android模拟器不支持恢复模式 只支持正常启动模式 Android 模拟器接受 Android 映像 包括内核和 ramdisk 映像 作为其命令行界面上的独立映像 它们不会被编译为一个大映像
  • Spring Boot 安全性和设置 security.basic.path

    我正在尝试使用 Spring Boot 它是自动配置 Spring Security 的自动配置未来 我按照教程操作http spring io guides gs secure web http spring io guides gs s
  • 如何将多个复选框值插入到表中?

    我似乎无法找到或弄清楚将多个复选框值从表单插入到表中的工作解决方案 我的结束是将仅一个复选框值的值插入到表中 请指出我如何插入多个复选框值 而不仅仅是一个 在下面找到我到目前为止所拥有的 My form
  • 如何突出显示页面上与单词数组匹配的所有单词?

    我想找到我的网页上与 Javascript 数组中的任何单词匹配的所有单词 并突出显示它们 将它们包装在特殊的 span 标签中 做到这一点最简单的方法是什么 我用jquery 不完美 但简单并且可能有效 var regex Hello G
  • 未对齐的内存访问

    我正在开发不支持未对齐内存访问的嵌入式设备 对于视频解码器 我必须处理 8x8 像素块中的像素 每个像素一个字节 该设备具有一些 SIMD 处理功能 使我能够并行处理 4 个字节 问题是 8x8 像素块不能保证从对齐的地址开始 并且函数需要
  • 使用 Python 全文搜索 XML 数据:最佳实践、优缺点

    Task 我想使用 Python 对 XML 数据进行全文搜索 示例数据
  • 将动态生成的表单嵌入到另一个表单中

    我使用 Symfony2 1 的 FormBuilder 在控制器中创建动态表单 form this gt createFormBuilder defaultData form gt add field text 我想以同样的方式嵌入另一个
  • 计算向量 v 矩阵的“v^T A v”

    我有一个k n矩阵 X 以及k k矩阵 A 对于每列X 我想计算标量 X i T dot A dot X i 或者 从数学角度来说 Xi A Xi 目前 我有一个for loop out np empty n for i in xrange
  • 如果为 TestFixture 级别设置“Property”属性,是否有可能获取 Nunit“Property”属性值

    这是我的 TestFixture 类 namespace TestApplication Tests TestFixture Property type smoke public class LoginFixture Test Proper
  • 使用 Linking.sendIntent() 打开设置

    我想在android中打开系统设置 根据文档 Linking sendIntent https facebook github io react native docs linking sendintent可以发送提到的意图安卓设置 htt
  • 另一个生命游戏问题(无限网格)?

    我一直在玩 Conway 的生命游戏 最近发现了一些令人惊讶的快速实现 例如 Hashlife 和 Golly 在这里下载Golly http golly sourceforge net http golly sourceforge net
  • 在 PyQt5 中,如何使用拖放正确移动 QTableView 中的行

    我 只是 希望能够使用QTableView用于移动现有行的拖放机制 我找到了很多来源 例如here https stackoverflow com questions 12168610 qtableview drag move rows h