社区首页 >问答首页 >Qt:如何使用自定义模型在QListView中实现简单的内部拖放来对项目进行重新排序

Qt:如何使用自定义模型在QListView中实现简单的内部拖放来对项目进行重新排序
EN

Stack Overflow用户
提问于 2019-06-29 16:23:28
回答 3查看 1.1K关注 0票数 4

我有一个自定义结构的QList,我正在使用自定义模型类(QAbstractListModel的子类)在一维QListView中显示这些结构。我已经重写了方法rowCountflagsdata,以从结构元素构造一个显示字符串。

现在,我想启用内部拖放功能,通过在其他项目之间拖放项目来对列表中的项目进行重新排序,但这项任务似乎非常复杂。我到底需要覆盖哪些内容,需要设置哪些参数?我试了很多东西,我试过了

代码语言:javascript
代码运行次数:0
复制
view->setDragEnabled( true );
view->setAcceptDrops( true );
view->setDragDropMode( QAbstractItemView::InternalMove );
view->setDefaultDropAction( Qt::MoveAction );

我试过了

代码语言:javascript
代码运行次数:0
复制
Qt::DropActions supportedDropActions() const override {
    return Qt::MoveAction;
}
Qt::ItemFlags flags( const QModelIndex & index ) const override{
    return QAbstractItemModel::flags( index ) | Qt::ItemIsDragEnabled;
}

我尝试实现了insertRowsremoveRows,但仍然不起作用。

我还没有找到一个代码可以完全做到这一点的例子。官方文档非常深入地介绍了视图/模型模式是如何工作的,以及如何从外部应用程序或其他小部件进行拖放,但我不想要这些。我只想简单的内部拖放手动重新排序的项目中的一个列表视图。

有人能帮帮我吗?否则我会为此发疯的。

编辑:根据请求添加insertRows/removeRows实现:

代码语言:javascript
代码运行次数:0
复制
bool insertRows( int row, int count, const QModelIndex & parent ) override
{
    QAbstractListModel::beginInsertRows( parent, row, row + count - 1 );

    for (int i = 0; i < count; i++)
        AObjectListModel<Object>::objectList.insert( row, Object() );

    QAbstractListModel::endInsertRows();
    return true;
}

bool removeRows( int row, int count, const QModelIndex & parent ) override
{
    if (row < 0 || row + count > AObjectListModel<Object>::objectList.size())
        return false;

    QAbstractListModel::beginRemoveRows( parent, row, row + count - 1 );

    for (int i = 0; i < count; i++)
        AObjectListModel<Object>::objectList.removeAt( row );

    QAbstractListModel::endRemoveRows();
    return true;
}

objectList为QList,其中Object为模板参数。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-06-29 22:17:25

当您想要在自定义模型中重新组织项目时,您必须实现所有需要的操作:-如何插入和删除行-如何获取和设置数据-如何序列化项目(构建mimedata) -如何取消序列化项目

使用QStringList作为数据源的自定义模型的示例:

模型的最小实现应该是:

代码语言:javascript
代码运行次数:0
复制
class CustomModel: public QAbstractListModel
{
public:
    CustomModel()
    {
        internalData = QString("abcdefghij").split("");
    }
    int rowCount(const QModelIndex &parent) const
    {
        return internalData.length();
    }
    QVariant data(const QModelIndex &index, int role) const
    {
        if (!index.isValid() || index.parent().isValid())
            return QVariant();
        if (role != Qt::DisplayRole)
            return QVariant();
        return internalData.at(index.row());
    }
private:
    QStringList internalData;   
};

我们必须添加插入/删除行和设置数据的方法:

代码语言:javascript
代码运行次数:0
复制
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole)
    {
        if (role != Qt::DisplayRole)
            return false;
        internalData[index.row()] = value.toString();
        return true;
    }
    bool insertRows(int row, int count, const QModelIndex &parent)
    {
        if (parent.isValid())
            return false;
        for (int i = 0; i != count; ++i)
            internalData.insert(row + i, "");
        return true;
    }
    bool removeRows(int row, int count, const QModelIndex &parent)
    {
        if (parent.isValid())
            return false;
        beginRemoveRows(parent, row, row + count - 1);
        for (int i = 0; i != count; ++i)
            internalData.removeAt(row);
        endRemoveRows();
        return true;
    }

对于拖放部分:

首先,我们需要定义一个mime类型来定义反序列化数据的方式:

代码语言:javascript
代码运行次数:0
复制
    QStringList mimeTypes() const
    {
        QStringList types;
        types << CustomModel::MimeType;
        return types;
    }

其中,CustomModel::MimeType是像"application/my.custom.model"这样的常量字符串

将使用canDropMimeData方法检查丢弃的数据是否合法。因此,我们可以丢弃外部数据:

代码语言:javascript
代码运行次数:0
复制
    bool canDropMimeData(const QMimeData *data,
        Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex& /*parent*/)
    {
        if ( action != Qt::MoveAction || !data->hasFormat(CustomModel::MimeType))
            return false;
        return true;
    }

然后,我们可以基于内部数据创建我们的mime数据:

代码语言:javascript
代码运行次数:0
复制
    QMimeData* mimeData(const QModelIndexList &indexes) const
    {
        QMimeData* mimeData = new QMimeData;
        QByteArray encodedData;

        QDataStream stream(&encodedData, QIODevice::WriteOnly);

        for (const QModelIndex &index : indexes) {
            if (index.isValid()) {
                QString text = data(index, Qt::DisplayRole).toString();
                stream << text;
            }
        }
        mimeData->setData(CustomModel::MimeType, encodedData);
        return mimeData;
    }

现在,我们必须处理丢弃的数据。我们必须反序列化mime数据,插入新行以将数据设置在正确的位置(对于Qt::MoveAction,旧行将被自动删除。这就是我们必须实现removeRows的原因):

代码语言:javascript
代码运行次数:0
复制
bool dropMimeData(const QMimeData *data,
        Qt::DropAction action, int row, int column, const QModelIndex &parent)
    {
        if (!canDropMimeData(data, action, row, column, parent))
            return false;

        if (action == Qt::IgnoreAction)
            return true;
        else if (action  != Qt::MoveAction)
            return false;

        QByteArray encodedData = data->data("application/my.custom.model");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;
        int rows = 0;

        while (!stream.atEnd()) {
            QString text;
            stream >> text;
            newItems << text;
            ++rows;
        }

        insertRows(row, rows, QModelIndex());
        for (const QString &text : qAsConst(newItems))
        {
            QModelIndex idx = index(row, 0, QModelIndex());
            setData(idx, text);
            row++;
        }

        return true;
    }

如果你想了解更多关于Qt中的拖放系统的信息,可以看看the documentation

票数 7
EN

Stack Overflow用户

发布于 2019-06-30 18:42:42

除了Romha的很好的答案之外,我还想补充一些关于它是如何工作的更多细节,以及它的困惑之处。

官方的documentation says QAbstractItemModelmimeTypesmimeDatadropMimeData的默认实现,只要你正确地实现了datasetDatainsertRowsremoveRows,它们就应该可以用于内部移动和复制操作。

从某种角度来看,他们是对的。它不会覆盖mimeDatadropMimeData,但仅当底层数据结构仅包含单个字符串时才有效,即从data返回并在setData中作为DisplayRole接收的字符串。当您有一个包含多个元素的复合对象列表(如我所拥有的),其中只有一个元素用于DisplayRole时,例如

代码语言:javascript
代码运行次数:0
复制
struct Elem {
    QString name;
    int i;
    bool b;
}

QVariant data( const QModelIndex & index, int role ) const override
{
    return objectList[ index.row() ].name;
}
bool setData( const QModelIndex & index, const QVariant & value, int role ) override
{
    objectList[ index.row() ].name = value.toString();
}

然后,缺省实现将实际执行此操作

代码语言:javascript
代码运行次数:0
复制
QVariant data = data( oldIndex, Qt::DisplayRole );
insertRows( newIndex, 1 )
setData( newIndex, data, Qt::DisplayRole )
removeRows( oldIndex, 1 )

因此,只有正确地移动名称,并保持结构的其余部分不变。这现在说得通了,但是这个系统太复杂了,我以前没有意识到。

因此,需要使用自定义mimeDatadropMimeData来移动结构的整个内容

票数 6
EN

Stack Overflow用户

发布于 2021-03-30 08:16:21

下面是一个用Python编写的明显的例子:

代码语言:javascript
代码运行次数:0
复制
import sys
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import (Qt, QStringListModel, QModelIndex,
                          QMimeData, QByteArray, QDataStream, QIODevice)
from PySide6.QtWidgets import (QApplication, QMainWindow, QListView, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)


class DragDropListModel(QStringListModel):
    def __init__(self, parent=None):
        super(DragDropListModel, self).__init__(parent)
        # self.myMimeTypes = 'application/vnd.text.list' # 可行

        # self.myMimeTypes = "text/plain" # 可行
        self.myMimeTypes = 'application/json'  # 可行

    def supportedDropActions(self):
        # return Qt.CopyAction | Qt.MoveAction  # 拖动时复制并移动相关项目
        return Qt.MoveAction  # 拖动时移动相关项目

    def flags(self, index):
        defaultFlags = QStringListModel.flags(self, index)

        if index.isValid():
            return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
        else:
            return Qt.ItemIsDropEnabled | defaultFlags

    def mimeTypes(self):
        return [self.myMimeTypes]

    # 直接将indexes里面对应的数据取出来,然后打包进了QMimeData()对象,并返回
    def mimeData(self, indexes):
        mmData = QMimeData()
        encodedData = QByteArray()
        stream = QDataStream(encodedData, QIODevice.WriteOnly)

        for index in indexes:
            if index.isValid():
                text = self.data(index, Qt.DisplayRole)
                stream << text  # 测试,也行
                # stream.writeQString(str(text))  # 原始, 可行

        mmData.setData(self.myMimeTypes, encodedData)
        return mmData

    def canDropMimeData(self, data, action, row, column, parent):
        if data.hasFormat(self.myMimeTypes) is False:
            return False
        if column > 0:
            return False
        return True

    def dropMimeData(self, data, action, row, column, parent):
        if self.canDropMimeData(data, action, row, column, parent) is False:
            return False

        if action == Qt.IgnoreAction:
            return True

        beginRow = -1
        if row != -1:  # 表示
            print("case 1: ROW IS NOT -1, meaning inserting in between, above or below an existing node")
            beginRow = row
        elif parent.isValid():
            print("case 2: PARENT IS VALID, inserting ONTO something since row was not -1, "
                  "beginRow becomes 0 because we want to "
                  "insert it at the beginning of this parents children")
            beginRow = parent.row()
        else:
            print("case 3: PARENT IS INVALID, inserting to root, "
                  "can change to 0 if you want it to appear at the top")
            beginRow = self.rowCount(QModelIndex())
        print(f"row={row}, beginRow={beginRow}")

        encodedData = data.data(self.myMimeTypes)
        stream = QDataStream(encodedData, QIODevice.ReadOnly)
        newItems = []
        rows = 0

        while stream.atEnd() is False:
            text = stream.readQString()
            newItems.append(str(text))
            rows += 1

        self.insertRows(beginRow, rows, QModelIndex())  # 先插入多行
        for text in newItems:  # 然后给每一行设置数值
            idx = self.index(beginRow, 0, QModelIndex())
            self.setData(idx, text)
            beginRow += 1

        return True


class DemoDragDrop(QWidget):
    def __init__(self, parent=None):
        super(DemoDragDrop, self).__init__(parent)

        # 设置窗口标题
        self.setWindowTitle('drag&drop in PySide6')
        # 设置窗口大小
        self.resize(480, 320)

        self.initUi()

    def initUi(self):
        self.vLayout = QVBoxLayout(self)
        self.listView = QListView(self)
        self.listView.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.listView.setDragEnabled(True)
        self.listView.setAcceptDrops(True)
        self.listView.setDropIndicatorShown(True)
        self.ddm = DragDropListModel()  # 该行和下面4行的效果类似
        # self.listView.setDragDropMode(QAbstractItemView.InternalMove)
        # self.listView.setDefaultDropAction(Qt.MoveAction)
        # self.listView.setDragDropOverwriteMode(False)
        # self.ddm = QStringListModel()

        self.ddm.setStringList(['Item 1', 'Item 2', 'Item 3', 'Item 4'])
        self.listView.setModel(self.ddm)

        self.printButton = QPushButton("Print")

        self.vLayout.addWidget(self.listView)
        self.vLayout.addWidget(self.printButton)

        self.printButton.clicked.connect(self.printModel)

    def printModel(self):  # 验证移动view中项目后,背后model中数据也发生了移动
        print(self.ddm.data(self.listView.currentIndex()))


if __name__ == '__main__':

    app = QApplication(sys.argv)
    app.setStyle('fusion')
    window = DemoDragDrop()
    window.show()
    sys.exit(app.exec_())
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56819085

复制
相关文章
Canvas画布
Canvas(画布)组件为Tkinter的图形绘制提供了基础 Canvas是一个通用的组件,通常用于显示和编辑图形。可以用它来绘制线段、圆形、多边形,甚至是绘制其他组件,创建图形编辑器,并实现各种自定义的小组件。
大白熊
2021/12/23
1.3K0
canvas清除画布-ZBrush中如何清除画布中多余图像
  ZBrush是一款数字雕刻与绘画软件,它以强大的功能和直观的工作流程彻底改变了整个三维行业。它的简洁化、智能化和人性化的设计无不让众多用户所折服。刚接触它的用户可能会因为找不到相关命令或不熟悉而觉得它有些复杂canvas清除画布,那么,在ZBrush®软件中如何对多余模型进行清除的操作有些刚接触的用户会找不清,本文就删除画布中的多余模型做详细讲解。
宜轩
2022/12/29
2.4K0
html5 canvas画布
<!DOCTYPE html> <html> <head>     <meta charset="UTF-8">     <title></title> </head> <body>     <canvas id="myCanvas" width="80px" height="80px" style="border: 1px solid #000000"></canvas>     <canvas id="myCanvas2"></canvas>     <canvas id="myCanvas3"></c
用户7718188
2021/11/01
9040
html5 canvas画布
本文由用户 AnnettaMcca 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
用户7705674
2021/09/22
1K0
canvas简易画布
今天学习了canvas,利用它做了一个简易版的画板,校验自己所学的知识,分享出来以供大家学习指教。先上效果图。
马克社区
2022/04/28
1.1K0
python canvas画布
canvas = Canvas(width=525, height=300, bg='white') # 0,0 is top left corner canvas.pack(expand=YES, fill=BOTH) # increases down, right
用户5760343
2022/05/13
1.4K0
python canvas画布
JointJS 拖动画布
效果 画布外套一层画板和一层画框,拖动画布时让画板在画框中滚动 源码 <div id="wrap" style="overflow: auto;height:300px;width:300px;background-color: darkgrey"> <div id="board" style="padding:20px"> <div id="paper"></div> </div> </div> <script> const paper = new joint.dia
路过君
2021/12/07
2K0
JointJS 拖动画布
flutter的画布认识
下图代码详见: p03_canvas/06_like_circle/paper.dart#_drawArcDetail
用户1974410
2022/09/20
3.2K0
flutter的画布认识
我的精益画布
商家推广是互联网重点关注的方向之一,现在也有很多产品、解决方案存在。但对于小商家来说,要不流程复杂、要不成本、门槛较高,这些产品都没有照顾到小商家身上,小商家依然使用低效的推广方法。
Jeffery
2019/07/30
1.4K0
我的精益画布
画布就是一切(一)— 画布编程的基本模式
我开发过基于QT的客户端程序、基于C# WinForm客户端,开发过Java后端服务,此外,前端VUE和React我也开发过不少。对应我所开发过的东西,比起一行一行冰冷的代码,我更加迷恋哪些能够直观的,可视化的东西。还记得以前在开发C#的时候,接触过一个的C# WinForm库NetronGraphLib,这个库能够让我们轻松的构建属于自己的流程图绘制软件,让我们能够以拖拉拽的方式来构建图(下图就是NetronGraphLib库的官方示例应用Cobalt):
w4ngzhen
2023/10/16
2660
画布就是一切(一)— 画布编程的基本模式
画布就是一切(一)— 画布编程的基本模式
我开发过基于QT的客户端程序、基于C# WinForm客户端,开发过Java后端服务,此外,前端VUE和React我也开发过不少。对应我所开发过的东西,比起一行一行冰冷的代码,我更加迷恋哪些能够直观的,可视化的东西。还记得以前在开发C#的时候,接触过一个的C# WinForm库NetronGraphLib,这个库能够让我们轻松的构建属于自己的流程图绘制软件,让我们能够以拖拉拽的方式来构建图(下图就是NetronGraphLib库的官方示例应用Cobalt):
w4ngzhen
2023/10/16
2140
画布就是一切(一)— 画布编程的基本模式
画布就是一切(一)— 画布编程的基本模式
我开发过基于QT的客户端程序、基于C# WinForm客户端,开发过Java后端服务,此外,前端VUE和React我也开发过不少。对应我所开发过的东西,比起一行一行冰冷的代码,我更加迷恋哪些能够直观的,可视化的东西。还记得以前在开发C#的时候,接触过一个的C# WinForm库NetronGraphLib,这个库能够让我们轻松的构建属于自己的流程图绘制软件,让我们能够以拖拉拽的方式来构建图(下图就是NetronGraphLib库的官方示例应用Cobalt):
w4ngzhen
2023/10/16
2700
画布就是一切(一)— 画布编程的基本模式
HTML5画布-小球碰撞
       html5是万维网的核心语言、标准通用标记语言下的一个应用超文本标记语言(HTML)的第五次重大修改。
阿珏
2018/08/08
1.7K0
Fabric.js 缩放画布 🍬
getZoom 可以获取画布当前缩放级别,用 setZoom 设置一个新的缩放级别。
德育处主任
2022/06/10
5.8K0
Fabric.js 缩放画布 🍬
组件注册与画布渲染
假如我们将可视化搭建整体定义为 <Designer>,那么 API 可能是这样的:
黄子毅
2023/02/14
1.3K0
组件注册与画布渲染
Fabric.js 清空画布,甚至连画布元素也给你干掉😏
上面的代码可以把清空画布,但如果没有了解过 canvas ,或者不熟悉的工友,看到上面的代码可能会有点懵。
德育处主任
2022/09/23
4.5K0
Fabric.js 清空画布,甚至连画布元素也给你干掉😏
摆地摊的商业画布
【字数:2098;阅读时长:10min】 作为产品经理,我们不可避免的一环就是思考所在公司、所在行业的商业模式! 今天我们用最朴实的摆地摊儿方式把商业画布解释一下 参考文献:《The One Tool Startups Need to Brainstorm, Test and Win》 个人对商业模式的理解是: 1、必须能够盈利 2、必须能自我保护 3、不是一成不变,必须是可调整的 现在我们将摆地摊和商业画布相继结合起来(叙述过程中会“串场”) 商业画布—— 地摊场景——准备摆地摊卖童装 1、用户细分 以用
用户2025931
2018/06/19
1K0
Fabric.js 拖拽平移画布
不过我们可以利用一些小技巧让画布具有被拖拽的能力,fabric.js 官网也提供了一个 demo ,但文档上并没有详细的讲解拖拽画布的实现原理。
德育处主任
2022/09/23
3.4K0
Fabric.js 拖拽平移画布
Android画布Canvas--区域Region
Canvas类有很多画图形的方法,除了常用的图形外,安卓还提供了Region--区域,表示Canvas图层上一块封闭的区域,可以用于将两个或多个图形做结合,还可以利用contains方法判断坐标、Rect是否在此区域中 构造方法有以下几种,可以传入一个Region,Rect ,或者上下左右四个坐标 /** Create an empty region */ public Region() { this(nativeConstructor()); }
aruba
2020/07/03
1.9K0
Android画布Canvas--区域Region
canvas画布实现矩形的绘制
绘制一个实心矩形cv.fillRect(x,y,width,height)绘制之前声明绘制的实心矩形颜色使用fillStyle
十月梦想
2018/08/29
2.6K0

相似问题

使用图像而不是画布

12

画布画布图像()不显示图像

12

旋转图像而不是画布

22

HTML画布图像没有显示?

14

HTML画布组合:在另一个画布下面显示画布图像

10
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档