Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Add reorder functionality to CustomListModel class using QStyledItemDelegate
Forum Updated to NodeBB v4.3 + New Features

Add reorder functionality to CustomListModel class using QStyledItemDelegate

Scheduled Pinned Locked Moved Unsolved Qt for Python
22 Posts 4 Posters 2.5k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G Geuse
    27 Feb 2023, 20:20

    I got a suggestion from ChatGPT and it kind of works better now. I'm not neglecting the help or effort all of you are providing. I'm just trying things to see if anything makes it act less weird. I understand I have a special use case and that I'm using Maya(though code wise it shouldn't be an issue).
    But I removed both the insertRows and removeRows and added moveRows instead and it works better actually. Though it doesn't insert them correct each time. There has to be something funky in how it calculates the height of the element?
    And I can't drag from below and insert above, then it crashes.
    I just thought I should post the code here

    import maya.OpenMayaUI as omui
    
    import os
    import sys
    
    from PySide2 import QtWidgets, QtGui, QtCore
    from shiboken2 import wrapInstance
    
    # スクリプトのフォルダ
    #CURRENT_PATH = os.path.dirname(__file__)
    CURRENT_PATH = os.path.normpath(r'D:\01_personal_files\20_MAYA_CUSTOM_CONFIG\10_maya_custom_config_2022\14_MODULES_DAG\16_dag_wip')
    # オリジナルのRole
    DESCRIPTION_ROLE = QtCore.Qt.UserRole
    STATUS_ROLE = QtCore.Qt.UserRole + 1
    THUMB_ROLE = QtCore.Qt.UserRole + 2
    
    # Modelに入るデータ
    SAMPLE_DATA = [
        {"name":"Ben", "description":"Support", "status":"OK", "color":[217,204,0], "thumbnail":"ben.png"},
        {"name":"Chris", "description":"Modeling", "status":"NOT OK", "color":[127,197,195], "thumbnail":"chris.png"},
        {"name":"Daniel", "description":"Manager", "status":"OK", "color":[217,204,166], "thumbnail":"daniel.png"},
        {"name":"Natalie", "description":"Sales", "status":"NOT OK", "color":[237,111,112], "thumbnail":"natalie.png"},
        {"name":"Mike", "description":"Animation", "status":"NOT OK", "color":[127,197,195], "thumbnail":"mike.png"}
    ]
    
    
    class CustomListModel(QtCore.QAbstractListModel):
    
        def __init__(self, parent=None, data=None):
    
            super(CustomListModel, self).__init__(parent)
            self.__items = data
    
        
        def rowCount(self, parent=QtCore.QModelIndex()):
            return len(self.__items)
    
        def data(self, index, role=QtCore.Qt.DisplayRole):
    
            if not index.isValid():
                return None
    
            if not 0 <= index.row() < len(self.__items):
                return None
    
            if role == QtCore.Qt.DisplayRole:
                return self.__items[index.row()]["name"]
    
            elif role == DESCRIPTION_ROLE:
                return self.__items[index.row()]["description"]
    
            elif role == STATUS_ROLE:
                return self.__items[index.row()]["status"]
    
            elif role == THUMB_ROLE:
                return self.__items[index.row()]["thumbnail"]
    
            elif role == QtCore.Qt.BackgroundRole:
                color = self.__items[index.row()]["color"]
                return QtGui.QColor(color[0], color[1], color[2])
    
            else:
                return None
    
        def moveRows(self, sourceParent, sourceRow, count, destinationParent, destinationChild):
            if sourceParent != destinationParent:
                return False
        
            self.beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild)
            for i in range(count):
                if destinationChild > sourceRow:
                    self.__items.insert(destinationChild - count + i, self.__items.pop(sourceRow))
                else:
                    self.__items.insert(destinationChild + i, self.__items.pop(sourceRow))
        
            self.endMoveRows()
            return True
    
    
        def supportedDropActions(self):
            return  QtCore.Qt.CopyAction | QtCore.Qt.MoveAction;
    
    
        def flags(self, index):
            defaultFlags = QtCore.QAbstractListModel.flags(self, index)
            
            if index.isValid():
                return QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | defaultFlags
            else:
                return QtCore.Qt.ItemIsDropEnabled | defaultFlags
    
    
    class CustomListDelegate(QtWidgets.QStyledItemDelegate):
    
        # Listの各アイテムの大きさ
        ITEM_SIZE_HINT = [300, 70]
    
        # 各アイテム内の表示に使う定数
        MARGIN = 20
        THUMB_AREA_WIDTH = 70
        THUMB_WIDTH = 60
        STATUS_AREA_WIDTH = 24
    
        # Painterが使うブラシやペンの定義
        BG_DEFAULT = QtGui.QBrush(QtGui.QColor(247,248,242))
        BG_SELECTED = QtGui.QBrush(QtGui.QColor(255,255,204))
        BORDER_DEFAULT = QtGui.QPen(QtGui.QColor(255,255,255), 0.5, QtCore.Qt.SolidLine)
        TEXT_BLACK_PEN = QtGui.QPen(QtGui.QColor(106,107,109), 0.5, QtCore.Qt.SolidLine)
        SHADOW_PEN = QtGui.QPen(QtGui.QColor(220,220,220, 100), 1, QtCore.Qt.SolidLine)
        FONT_H1 = QtGui.QFont("Corbel", 12, QtGui.QFont.Normal, True)
        FONT_H2 = QtGui.QFont("Corbel", 11, QtGui.QFont.Normal, True)
    
    
        def __init__(self, parent=None):
    
            super(CustomListDelegate, self).__init__(parent)
    
    
    
        # 背景描画メソッド paintメソッド内で呼ばれている
        def drawBackground(self, painter, rect, selected, color):
    
            if selected:
                baseColor = self.BG_SELECTED
    
            else:
                baseColor = self.BG_DEFAULT
    
            painter.setPen(self.BORDER_DEFAULT)
            painter.setBrush(baseColor)
            painter.drawRoundedRect(rect, 1, 1)
    
            painter.setPen(self.SHADOW_PEN)
            painter.drawLine(rect.bottomLeft().x(), rect.bottomLeft().y()+2, rect.bottomRight().x(), rect.bottomRight().y()+2)
    
            r = QtCore.QRect(rect.left(),
                             rect.top(),
                             self.THUMB_AREA_WIDTH,
                             rect.height())
    
            painter.setPen(QtCore.Qt.NoPen)
            painter.setBrush(QtGui.QBrush(color))
            painter.drawRoundedRect(r, 1, 1)
    
    
        # サムネイル描画メソッド paintメソッド内で呼ばれている
        def drawThumbnail(self, painter, rect, thumbnail):
    
            r = QtCore.QRect(rect.left() + (self.THUMB_AREA_WIDTH - self.THUMB_WIDTH) / 2,
                             rect.top() + (self.THUMB_AREA_WIDTH - self.THUMB_WIDTH) / 2,
                             self.THUMB_WIDTH,
                             self.THUMB_WIDTH)
            thumbImage = QtGui.QPixmap(os.path.join(CURRENT_PATH, "images", thumbnail)).scaled(self.THUMB_WIDTH, self.THUMB_WIDTH)
            painter.drawPixmap(r, thumbImage)
    
    
        # 名前テキスト描画メソッド paintメソッド内で呼ばれている
        def drawName(self, painter, rect, name):
    
            painter.setFont(self.FONT_H1)
            painter.setPen(self.TEXT_BLACK_PEN)
            r = QtCore.QRect(rect.left() + self.THUMB_AREA_WIDTH + self.MARGIN,
                             rect.top(),
                             rect.width() - self.THUMB_AREA_WIDTH - self.STATUS_AREA_WIDTH - self.MARGIN * 3,
                             rect.height() / 2)
            painter.drawText(r, QtCore.Qt.AlignVCenter|QtCore.Qt.AlignLeft, "Name : " + name)
            painter.setPen(self.TEXT_BLACK_PEN)
            painter.drawLine(r.bottomLeft(), r.bottomRight())
    
    
        # ディスクリプションテキスト描画メソッド paintメソッド内で呼ばれている
        def drawDescription(self, painter, rect, description):
    
            painter.setFont(self.FONT_H2)
            painter.setPen(self.TEXT_BLACK_PEN)
            r = QtCore.QRect(rect.left() + self.THUMB_AREA_WIDTH + self.MARGIN,
                             rect.top() + rect.height() / 2,
                             rect.width() - self.THUMB_AREA_WIDTH - self.STATUS_AREA_WIDTH - self.MARGIN * 3,
                             rect.height() / 2)
            painter.drawText(r, QtCore.Qt.AlignVCenter|QtCore.Qt.AlignLeft, description)
    
    
        # ステータス画像描画メソッド paintメソッド内で呼ばれている
        def drawStatus(self, painter, rect, status):
            painter.setFont(self.FONT_H2)
            painter.setPen(self.TEXT_BLACK_PEN)
            r = QtCore.QRect(rect.right() - self.STATUS_AREA_WIDTH - self.MARGIN,
                             rect.center().y() - self.STATUS_AREA_WIDTH / 2,
                             self.STATUS_AREA_WIDTH,
                             self.STATUS_AREA_WIDTH)
            statusIcon = "notok.png"
    
            if status == "OK":
                statusIcon = "ok.png"
    
            statusImage = QtGui.QPixmap(os.path.join(CURRENT_PATH, "images", statusIcon))
            painter.drawPixmap(r, statusImage)
    
    
        # 描画メインメソッド
        def paint(self, painter, option, index):
    
            selected = False
    
            if option.state & QtWidgets.QStyle.State_Selected:
                selected = True
    
            name = index.data(QtCore.Qt.DisplayRole)
            description = index.data(DESCRIPTION_ROLE)
            status = index.data(STATUS_ROLE)
            color = index.data(QtCore.Qt.BackgroundRole)
            thumbnail = index.data(THUMB_ROLE)
    
            self.drawBackground(painter, option.rect, selected, color)
            self.drawThumbnail(painter, option.rect, thumbnail)
            self.drawName(painter, option.rect, name)
            self.drawDescription(painter, option.rect, description)
            self.drawStatus(painter, option.rect, status)
    
    
        # 各アイテムの大きさ
        def sizeHint(self, option, index):
    
            return QtCore.QSize(self.ITEM_SIZE_HINT[0], self.ITEM_SIZE_HINT[1])
    
    
    
    
            
            
            
    #----------------------------------------------------------------------------
    ## メインUI
    
    def maya_main_window():
        main_window_ptr = omui.MQtUtil.mainWindow()
        return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
        
    
    class GUI(QtWidgets.QMainWindow):
    
        def __init__(self, parent=maya_main_window()):
            super(GUI, self).__init__(parent)
    
            self.setWindowTitle('Main Window')
            self.resize(350, 600)
            self.setMaximumSize(525, 700)
            self.initUI()
    
        def initUI(self):
            # リストの設定
            myListView = QtWidgets.QListView(self)
            myListView.setSpacing(10)
            myListView.setAutoFillBackground(False)
            url = os.path.join(CURRENT_PATH, "images", "bg.png").replace("\\", "/")
            myListView.setStyleSheet("background: url("+url+") center center;")
            myListView.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
                    
            # modelを作る
            myListModel = CustomListModel(data = SAMPLE_DATA)
            # delegateを作る
            myListDelegate = CustomListDelegate()
    
            # listview
            myListView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
            myListView.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
            myListView.setDefaultDropAction(QtCore.Qt.MoveAction)
            myListView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
            myListView.setDragEnabled(True)
            myListView.setAcceptDrops(True)
            myListView.setDropIndicatorShown(True)
    
            # modelとdelegateをリストにセット
            myListView.setItemDelegate(myListDelegate)
            myListView.setModel(myListModel)
            #
            self.setCentralWidget(myListView)
    
    
    
    
    #
    if __name__ == "__main__":
        try:
            test_dialog.close()
            test_dialog.deleteLater()
        except:
            pass
    
        test_dialog = GUI()
        test_dialog.show()
    
    SGaistS Offline
    SGaistS Offline
    SGaist
    Lifetime Qt Champion
    wrote on 27 Feb 2023, 20:28 last edited by
    #21

    @Geuse you should temporarily remove all code unrelated to the model. Just keep the list view and the model itself to do your testing.

    Interested in AI ? www.idiap.ch
    Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

    G 1 Reply Last reply 27 Feb 2023, 20:51
    0
    • SGaistS SGaist
      27 Feb 2023, 20:28

      @Geuse you should temporarily remove all code unrelated to the model. Just keep the list view and the model itself to do your testing.

      G Offline
      G Offline
      Geuse
      wrote on 27 Feb 2023, 20:51 last edited by Geuse
      #22

      @SGaist said in Add reorder functionality to CustomListModel class using QStyledItemDelegate:

      @Geuse you should temporarily remove all code unrelated to the model. Just keep the list view and the model itself to do your testing.

      Thanks.
      I commented out my delegate and eventually I changed my spacing in my ListView from 10 to 0.

      myListView.setSpacing(0)
      

      and would you know, that fixed it...
      Now the dragging and dropping works both ways. It just doesn't look as nice anymore., maybe I can fix this in the delegate instead.

      And well, when dropping you have to hit it right in between where none of the other elements get highlighted, then it works perfect so I should be happy. Thanks for all the help and patience with me.

      Is it possible to make it look like the animation I posted here below? Can you style it or code it to behave that way?
      Add animation to the elements so they are pushed to open up an empty space in between the rows and simultaneously remove the item I'm dragging from its own position in the list?

      alt text

      1 Reply Last reply
      0

      21/22

      27 Feb 2023, 20:28

      • Login

      • Login or register to search.
      21 out of 22
      • First post
        21/22
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved