Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QTreeView drag/drop handling
Forum Updated to NodeBB v4.3 + New Features

QTreeView drag/drop handling

Scheduled Pinned Locked Moved Unsolved General and Desktop
21 Posts 3 Posters 9.8k Views 1 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.
  • Oak77O Offline
    Oak77O Offline
    Oak77
    wrote on last edited by
    #1

    I have some obvious questions for which I wasn't able to find satisfactory answers.

    1. Is there a way (subclass, etc.) to have access to a kind of drag/drop event, so that we could control a dropped item?

    I.e. in the example, if we create a copy of item by dragging, it's QStandardItemModel row gets copied, so it gets the same ID and Name. That is obviously unacceptable, ID should be a new one (typically obtained from a database) and Name should be somtehing like "OriginalName_1".

    1. Is there a way to control, whether an item can be created on drop or not?

    I.e. Toronto shouldn't be allowed to be dropped into United States, but let's pretend it can be dropped to another Canadian state. We need to know where it is being dropped and then have a way to create a record and tree item or do nothing (based on custom function evaluating the items).

    1. Is there a way to set QTreeView in a way, that drag&drops within a QTreeView would move items (they're copied by default), while dropping from another QTreeView would copy an item (that works)?

    I found few threads on this subject, but they don't seem to answer the question and got me perhaps even more confused.

    Here is a working sample code I use for testing.

    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow, QTreeView, QTreeWidgetItemIterator, QAction
    from PyQt5.Qt import QStandardItemModel, QStandardItem
    from PyQt5.QtGui import QFont, QColor
     
     
    class StandardItem(QStandardItem):
        def __init__(self, txt='', font_size=12, set_bold=False, color=QColor(0, 0, 0)):
            super().__init__()
     
            fnt = QFont('Open Sans', font_size)
            fnt.setBold(set_bold)
     
            self.setEditable(False)
            self.setForeground(color)
            self.setFont(fnt)
            self.setText(txt)
     
     
    class AppDemo(QMainWindow):
        def __init__(self):
            super().__init__()
            self.setWindowTitle('World Country Diagram')
            self.resize(500, 700)
            self.tb = self.addToolBar("TestBar")  
                    
            LstTr = QAction("List tree",self)
            LstTr.triggered.connect(self.ListTree)    
            self.tb.addAction(LstTr) 
     
            treeView = QTreeView()
            treeView.setHeaderHidden(True)     
            treeView.setDragEnabled(True)
            treeView.setAcceptDrops(True)
     
            self.treeModel = QStandardItemModel()
            rootNode = self.treeModel.invisibleRootItem()
     
            Data = (    # Database-like test data
                #ID       Parent ID       Type      Name              Prop1
                #---------------------------------------------------  ----------
                (1,           -1,          1,       'United States',    'USA'          ),
                (3,            1,          2,       'California',       'CA'           ),
                (4,            3,          3,       'Oakland',          '-'            ),
                (5,            3,          3,       'San Francisco',    '-'            ),
                (6,            3,          3,       'San Jose',         '-'            ),
                (7,            1,          2,       'Texas',            'TX'           ),
                (8,            7,          3,       'Austin',           '-'            ),
                (9,            7,          3,       'Houston',          '-'            ),
                (10,           7,          3,       'Dallas',           '-'            ),
                (11,          -1,          1,       'Canada',           'CAN'          ),
                (12,          11,          2,       'Alberta',          ''             ),
                (13,          11,          2,       'British Columbia', ''             ),
                (14,          11,          2,       'Ontario',          ''             ),
                (15,          14,          3,       'Toronto',          '-'            ),
                (16,          13,          3,       'Vancouver',        '-'            ),
                (17,          12,          3,       'Calgary',          '-'            ),
                (18,          13,          3,       'Wells',            '-'            )
                ) 
    
            root = self.treeModel.invisibleRootItem()
            for ID, ParentID, Type, Name, Prop1 in Data:  # for each record, generate an item in a treeModel, used in QTreeView
                print("ID = " + str(ID))
                print("Name = " + Name)
                it = QStandardItem(Name)
                it.setData(ID) #, Name)
                if ParentID == -1:
                    self.treeModel.appendRow(it)
                for item in self.iterItems(root):
                    #print(item.text())
                    if item.data() == ParentID:
                        item.appendRow(it)
     
            treeView.setModel(self.treeModel)
            treeView.expandAll()
            treeView.doubleClicked.connect(self.getValue)
            self.treeModel.itemChanged.connect(self.ModelItemChanged)
     
            self.setCentralWidget(treeView)
     
        def getValue(self, val):
            print(val.data())
            print(val.row())
            print(val.column())
            print("-------------")
            #self.listTree()
            
        def ModelItemChanged(self, val):
            print("Model item changed")
            print(val.data())
            print(val.row())
            print(val.column())
            print("-------------")
            
        def listTree(self):
            for it in self.treeModel:
                print(str(it))
    
                 
        def iterItems(self, root):    # get all items, credits to @ekhumuro answer
            def recurse(parent):
                for row in range(parent.rowCount()):
                    for column in range(parent.columnCount()):
                        child = parent.child(row, column)
                        yield child
                        if child.hasChildren():
                            yield from recurse(child)
            if root is not None:
                yield from recurse(root) 
     
        def ListTree(self):
            print("================================================================")
            root = self.treeModel.invisibleRootItem()
            for item in self.iterItems(root):
                print(item.text() + "      " + str(item.data()) )
            print("================================================================")      
     
    app = QApplication(sys.argv)        
     
    demo = AppDemo()
    demo.show()
     
    sys.exit(app.exec_())
    

    Double click prints current item. On drop a ItemChanged is fired and printed a message. A button "ListTree" lists all treeModel rows - comparing before and after shows how a new row was added.

    I took this tutorial and heavily modified, with my adjustments, some mods were based on @ekhumuro SO answers. Credits to those authors.

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      Did you already read the Using Drag & Drop with item views chapter in Qt's documentation ?

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

      Oak77O 1 Reply Last reply
      1
      • SGaistS SGaist

        Hi,

        Did you already read the Using Drag & Drop with item views chapter in Qt's documentation ?

        Oak77O Offline
        Oak77O Offline
        Oak77
        wrote on last edited by
        #3

        @SGaist Well, to be honest I probably didn't. I went through documentation, but I'd start at places like QTreeView or QStandardItemModel, basically stuff I can see employed in the code.

        I read it now and I can see some helpful sections, but as soon as I started to test them I hit walls. I will try to search for different references to find a way out by myself first, though. Thank you for pointing that piece of documentation out.

        1 Reply Last reply
        0
        • Oak77O Offline
          Oak77O Offline
          Oak77
          wrote on last edited by
          #4

          Basically months (of some spare time) into the research and testing, I'm still unable to do this simple task. The documentation and threads are fragmented between different ways of doing the same thing and I basically found none of them completely describing this very basic stuff.

          I am including my latest test file, where I have an issue I've been fighting for couple of last weeks. Currently it crashes every time I try to use internalPointer() of isValid() == true index.

          from PyQt5 import QtCore, QtGui
          from PyQt5.QtCore import Qt, QSize, QAbstractItemModel, pyqtSlot, pyqtSignal, QModelIndex
          from PyQt5.QtWidgets import QWidget, QMainWindow, QTextEdit, QDockWidget, QToolBar, QTabWidget, QMenu, QAction, QLayout, QTabBar, QMenu, QToolButton, QTableView, QHeaderView, QHBoxLayout, QVBoxLayout, QGridLayout, QLabel, QSplitter, QLineEdit, QSpacerItem, QSizePolicy, QPushButton, QComboBox, QDateEdit, QTreeWidget, QTreeWidgetItem, QGroupBox, QTreeView, QSpinBox, QCheckBox, QDoubleSpinBox, QRadioButton, QDateEdit, QTimeEdit, QDateTimeEdit
          import PyQt5.QtWidgets                                                                         
          from PyQt5.QtGui import QIcon, QPixmap, QStandardItemModel, QStandardItem, QPalette, QColor, QIntValidator, QDoubleValidator
          import _pickle as cPickle
          
          _DOCK_OPTS = PyQt5.QtWidgets.QMainWindow.AllowNestedDocks
          _DOCK_OPTS |= PyQt5.QtWidgets.QMainWindow.AllowTabbedDocks
          
           
          class TreeModel(QStandardItemModel):
              def __init__(self):
                  QStandardItemModel.__init__(self)
                  
              def supportedDropActions(self):
                  return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction 
          
              def mimeData(self, indexes):
                  mimedata = QtCore.QMimeData()
                  #mimedata.setData('text/xml', str(self.nodeFromIndex(indexes[0])))
                  #print("xxx mimeData = " + str(mimeData))
                  print("Starting mimeData....")
                  print("Index:    " + str(indexes))
                  print("------")
                  print(str(self.nodeFromIndex(indexes[0])))
                  print("------2")
                  node = self.nodeFromIndex(indexes[0])
                  print("Continuing mimeData....")
                  #print("Node:   " + str(node.data()))
                  #mimedata.setData('text/xml', str(node))
                  #bstream = cPickle.dumps(node)
                  #mimedata.setData('bstream', bstream)
                  print("Continuing mimeData 2....")
                  mimedata.setData('text/xml', 'mimeData')
                  return mimedata
          
              def nodeFromIndex(self, index):
                  print("Index2:    " + str(index))      
                  ip = None
                  if index.isValid() == True:
                      ip = index.internalPointer()
                      print("isValid...")
                      print(str(ip))
                  else:
                      print(str(self.root))
                      ip = self.root
                  print("before return")
                  return ip
                  ##return index.internalPointer() if index.isValid() else self.root
                  
          
              def mimeTypes(self):
                  return ['text/xml']
          
              #def mimeData(self, indexes):
                  #mimedata = QtCore.QMimeData()
                  #mimedata.setData('text/xml', 'mimeData')
                  #return mimedata
          
              def dropMimeData(self, data, action, row, column, parent):
                  print('dropMimeData %s %s %s %s' % (data.data('text/xml'), action, row, parent))
                  return True
              
              def flags(self, index):
                  if not index.isValid():
                      return QtCore.Qt.ItemIsEnabled
                  return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
          
          # -----------------------------------------------------------------------------------------------------------------------------------    
          
          class Window(QMainWindow):
              selIdxsSignal = pyqtSignal(QModelIndex)
              
              def __init__(self):
                  QMainWindow.__init__(self)
                  self.setWindowTitle("Test")
                  self.resize(600,500)
          
                  self.tb = self.addToolBar("Test") 
                  self.ShowTitleBars = False
                  self.setWidth = 1600
                  secondQMainWindow = QMainWindow()
                  self.detailWidget = QWidget()
                  self.detailLayout = QVBoxLayout()
                  self.detailWidget.setLayout(self.detailLayout)
          
                  self.detailData = (    # Database-like test data            
                      (1,       -1,     20,   'GroupA',       'Group A'      ,400,   0,    '',    False,   8,   '',   '',    0),
                      (3,        1,      3,   'ItemA1',       'Item A1'      , 50,   0,    '',    False,   8,   '',   '',    0),
                      (4,        1,      1,   'ItemA2',       'Item A2'      ,  0,   0,    '',    True,    8,   '',   '',    0),
                      (5,        1,      8,   'ItemA3',       'Item A3'      , 90,   0,    '',    False,   8,   '',   '',    0),
                      (6,        1,     10,   'ItemA4',       'Item A4'      ,120,   0,    '',    False,   8,   '',   '',    0),
                      (7,        1,      4,   'ItemA5',       'Item A5'      , 70,   0,    ' kg', True,    8,   '',   '',    2),
                      (8,       -1,     20,   'GroupB',       'Group B'      ,  0,   0,    '',    False,   8,   '',   '',    0),
                      (9,        8,      7,   'ItemB1',       'Item B1'      ,  0,   0,    '',    True,    8,   '',   '',    0),
                      (10,       8,      2,   'ItemB2',       'Item B2'      ,  0,   66,   '',    True,    8,   '',   '',    0),
                      )
                  
                  self.sourceData = (    # Database-like test data 8,       '',        '',           0),
                      (1,       -1,      1,   'Item1',   'Item 1',     0,     0,    '',    False,     8,    '',     '',    0),
                      (2,       -1,      2,   'Item2',   'Item 2',     0,     0,    '',    False,     8,    '',     '',    0),
                      (3,       -1,      3,   'Item3',   'Item 3',     0,     0,    '',    False,     8,    '',     '',    0),
                      (4,       -1,      4,   'Item4',   'Item 4',     0,     0,    '',    False,     8,    '',     '',    0),
                      (5,       -1,      5,   'Item5',   'Item 5',     0,     0,    '',    False,     8,    '',     '',    0),
                      (6,       -1,      6,   'Item6',   'Item 6',     0,     0,    '',    False,     8,    '',     '',    0),
                      (7,       -1,      7,   'Item7',   'Item 7',     0,     0,    '',    False,     8,    '',     '',    0),
                      (8,       -1,      8,   'Item8',   'Item 8',     0,     0,    '',    False,     8,    '',     '',    0),
                      (9,       -1,      9,   'Item9',   'Item 9',     0,     0,    '',    False,     8,    '',     '',    0),
                      (10,      -1,     10,   'Item10',  'Item 10',    0,     0,    '',    False,     8,    '',     '',    0),
                      (11,      -1,     11,   'Item11',  'Item 11',    0,     0,    '',    False,     8,    '',     '',    0),
                      (12,      -1,     12,   'Item12',  'Item 12',    0,     0,    '',    False,     8,    '',     '',    0),
                      (20,      -1,     20,   'Item13',  'Item 13',    0,     0,    '',    False,     8,    '',     '',    0),
                      (21,      -1,     21,   'Item14',  'Item 14',    0,     0,    '',    False,     8,    '',     '',    0),      
                      )
           
                  self.setTabPosition (Qt.LeftDockWidgetArea, QTabWidget.North)
                  self.central = secondQMainWindow
                  self.setDockOptions(_DOCK_OPTS)   #.dockNestingEnabled(True)
                  self.dw1 = QDockWidget("Pre-set items")
                  self.dw1.topLevelChanged.connect(self.HideTitleBar)
                  self.dw1.setTitleBarWidget(QWidget(self.dw1))
          
          
                  srcTreeView = QTreeView()
                  srcTreeView.setIconSize(QSize(24,24))
                  srcTreeView.setHeaderHidden(True)   
                  srcTreeView.setDragEnabled(True)
                  self.srcModel = TreeModel()
                  srcroot = self.srcModel.invisibleRootItem()
                  srcroot1 = QStandardItem("Group 1")
                  srcroot1.setIcon(QIcon(QPixmap('detail.png')))
                  srcroot.appendRow(srcroot1)
                  srcroot2 = QStandardItem("Group 2")
                  srcroot2.setIcon(QIcon(QPixmap('detail.png')))
                  srcroot.appendRow(srcroot2)
                  srcroot3 = QStandardItem("Group 3")
                  srcroot3.setIcon(QIcon(QPixmap('detail.png')))
                  srcroot.appendRow(srcroot3)
                  for ID, ParentID, Type, Name, Label, Width, Height, Units, IsEditable, FontSize, FontVariant, NumberFormat, DecimalPlaces in self.sourceData:  # for each record, generate an item in a treeModel, used in QTreeView
                      print("ID = " + str(ID))
                      print("Name = " + Name)
                      it = QStandardItem(Name)
                      it.setData(ID) #, Name)
                      it.setIcon(QIcon(self.getIconByType(Type)))
                      if ParentID == -1:
                          srcroot1.appendRow(it)
                      for item in self.iterItems(srcroot):
                          #print(item.text())
                          if item.data() == ParentID:
                              item.appendRow(it)
                  srcTreeView.setModel(self.srcModel)
                  self.dw1.setWidget(srcTreeView)
                  srcTreeView.expandAll()
                  srcTreeView.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)
                 
                  
                  SourceTree = QTreeView()
                          
                  self.addDockWidget(Qt.LeftDockWidgetArea, self.dw1)
          
                         
                  self.dw4 = QDockWidget("Group Editor")
                  self.dw4.topLevelChanged.connect(self.HideTitleBar)
                  self.dw4.setTitleBarWidget(QWidget(self.dw4))        
                  DetailW = QWidget()
                  DetailW.setAcceptDrops(True)
                  DetailSplitter = QSplitter()
                  self.DetailTree = QTreeView()  
                  self.DetailTree.setDragEnabled(True)
                  self.treeModel = TreeModel()
                  self.DetailTree.setHeaderHidden(True)   
                  self.DetailTree.setAcceptDrops(True)
                  self.DetailTree.setIconSize(QSize(24,24))
                  DetailSplitter.addWidget(self.DetailTree)
                  DetailSplitter.addWidget(self.detailWidget)
                  self.dw4.setWidget(DetailSplitter)
                  self.DetailTree.setObjectName = "DetailEditor"
                  print("Check model:   " + str(self.DetailTree.model))
          
                  
                  root = self.treeModel.invisibleRootItem()
                  root1 = QStandardItem("Master Group")
                  root1.setIcon(QIcon(QPixmap('detail.png')))
                  root.appendRow(root1)
                  for ID, ParentID, Type, Name, Label, Width, Height, Units, IsEditable, FontSize, FontVariant, NumberFormat, DecimalPlaces in self.detailData:  # for each record, generate an item in a treeModel, used in QTreeView
                      print("ID = " + str(ID))
                      print("Name = " + Name)
                      it = QStandardItem(Name)
                      it.setData(ID) #, Name)
                      it.setIcon(QIcon(self.getIconByType(Type)))
                      if ParentID == -1:
                          root1.appendRow(it)
                      for item in self.iterItems(root):
                          #print(item.text())
                          if item.data() == ParentID:
                              item.appendRow(it)
           
                  self.DetailTree.setModel(self.treeModel)
                  self.DetailTree.expandAll()
                  self.DetailTree.selectionModel().selectionChanged.connect(self.selIdxSignalChangeHandler)  
                  self.selIdxsSignal.connect(self.selectionChangeHandler) 
                          
                  
                  DetailGB1 = QGroupBox()
                  DetailGB1.setMaximumWidth(300)
                  self.addDockWidget(Qt.RightDockWidgetArea, self.dw4)    
                
          
                  btn = QAction("A button",self)
                  self.tb.addAction(btn)
                                          
                  barTog = QAction(QIcon("arrange_24x24.png"),"Toggle Title Bars",self)
                  barTog.setCheckable(True)
                  barTog.triggered.connect(self.ToogleTitles2)
                  self.tb.addAction(barTog)
                  
                  for widget in self.children():
                      if isinstance(widget, QTabBar):
                          widget.setTabsClosable(True)
                          widget.setCurrentIndex(0)
                          break
                  
          
              def selIdxSignalChangeHandler(self):
                  self.selIdxsSignal.emit(self.DetailTree.selectionModel().selectedIndexes()[0])
                  
              def selectionChangeHandler(self, Idx):
                  print(str(Idx))
                  IID = Idx.data(Qt.UserRole + 1)
                  print("    Selection changed - IID = " + str(IID))
                  tbName = self.findChild(QLineEdit, "txtText")
                  tbWidth = self.findChild(QLineEdit, "txtWidth")
                  tbHeight = self.findChild(QLineEdit, "txtHeight")
                  tbDataType = self.findChild(QLineEdit, "txtType")
                  tbUnit = self.findChild(QLineEdit, "txtUnit")
                  tbDecimals = self.findChild(QLineEdit, "txtDecimals")
                  row = self.getRowByIndex(Idx)
                  if IID != None:
                      print("Assigning properties")
                      #tbName.setText(str(row[3]))
                      #tbWidth.setText(str(row[5]))
                      #tbHeight.setText(str(row[6]))
                      #tbDataType.setText(str(row[2]))
                      #tbUnit.setText(str(row[7]))
                      #tbDecimals.setText(str(row[12]))
          
              def getIconByType(self, Type):
                  iconList = [
                             (1, "type1.png"),
                             (3, "type2.png"),
                             (2, "type3.png"),
                             (4, "type4.png"),
                             (5, "type5.png"),
                             (6, "type6.png"),
                             (7, "type7.png"),
                             (8, "type8.png"),
                             (9, "type9.png"),
                             (10, "type10.png"),
                             (11, "type11.png"),
                             (12, "type12.png"),
                             (13, "type13.png"),
                             (14, "type14.png"),
                             (15, "type15.png"),
                             (16, "type16.png")
                             ]
                  for TypeID, Icon in iconList:
                      if TypeID == Type:
                          return Icon
                          break
              
              def getRowByIndex(self, Index):
                  model = self.treeModel
                  print("### getRowByIndex")
                  SelID = model.data(Index, Qt.UserRole+1)
                  print("### Selected ID = " + str(SelID))
                  row = None
                  for i, ir in enumerate(self.detailData):
                      if ir[0] == SelID:
                          row = self.detailData[i]
                          break
                  return row
          
                     
              def iterItems(self, root):    # get all items, credits to @ekhumuro answer
                  def recurse(parent):
                      for row in range(parent.rowCount()):
                          for column in range(parent.columnCount()):
                              child = parent.child(row, column)
                              yield child
                              if child.hasChildren():
                                  yield from recurse(child)
                  if root is not None:
                      yield from recurse(root) 
           
              def dropMimeData(self, mimedata, action, row, column, parentIndex):
                  if action == Qt.IgnoreAction: 
                      return True
                  dragNode = mimedata.instance() 
                  parentNode = self.nodeFromIndex(parentIndex) 
                  newNode = deepcopy(dragNode) #<------ why copy? Why not just reparent?
                  newNode.setParent(parentNode) 
                  self.insertRow(len(parentNode)-1, parentIndex) 
                  self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex) 
                  print("Done mime data")
                  return True 
          
                  
              def CreateNewTab(self):
                  WNoStr = str(self.CountDockWidgets())
                  dw = QDockWidget(WNoStr)
                  dw.topLevelChanged.connect(self.HideTitleBar)
                  dw.setTitleBarWidget(QWidget(dw))
                  textArea = QTextEdit()
                  textArea.setText("Text area " + WNoStr)
                  dw.setWidget(textArea)
                  dw.topLevelChanged.connect(self.HideTitleBar)
                  self.addDockWidget(Qt.LeftDockWidgetArea, dw)
                  self.tabifyDockWidget(self.dw1, dw)        
                          
              def HideTitleBar(self):
                  dockw = self.sender()
                  if dockw.isFloating() == False and self.ShowTitleBars == False:
                      dockw.setTitleBarWidget(QWidget(dockw))
          
              def ToogleTitles2(self):        
                  if self.ShowTitleBars == True:
                      self.ShowTitleBars = False
                  else:
                      self.ShowTitleBars = True
                  for widget in self.children():
                      if isinstance(widget, QDockWidget):
                          if widget.titleBarWidget() == None and self.ShowTitleBars == False:
                              if widget.isFloating() == False:
                                  widget.setTitleBarWidget(QWidget(widget))                        
                          else:
                              widget.setTitleBarWidget(None)              
                  print("Test Toggle")
                 
                  
              def SelectionEvent(self, selIdxs):
                  print("Selection changed!!!!!!!!!!!!!!!")
          
              @pyqtSlot("QModelIndex", "QModelIndex", "QVector<int>")
              def CurrentItemChanged(self, tep_left, bottom_right, roles):
                  print("CurrentItemChanged")
                  snd = self.sender()
                  print("Sender = " + str(snd) + "         " + str(snd.objectName()))
                  print("Sender Data = " + str(snd.data))
                  print("Sender Data = " + str(tep_left.data))
                  getRowByIndex(top_left)   # <<< based on index, do stuff
                  #print("Selected iems = " + str(selItems))
              
              def DroppedItem(self):
                  print("Item dropped...")
                   
                      
          # ----------------------------------------------------------------------------------------------------------------------------------------------------------------            
                                 
          if __name__ == '__main__':
              import sys
              app = PyQt5.QtWidgets.QApplication(sys.argv)
              window = Window()
              window.show()
              app.exec_()
          

          I don't think I can overcome it on my own, having basically no working example of what I'm trying to achieve, so I would like to ask the community here for advice.

          JonBJ 1 Reply Last reply
          0
          • Oak77O Oak77

            Basically months (of some spare time) into the research and testing, I'm still unable to do this simple task. The documentation and threads are fragmented between different ways of doing the same thing and I basically found none of them completely describing this very basic stuff.

            I am including my latest test file, where I have an issue I've been fighting for couple of last weeks. Currently it crashes every time I try to use internalPointer() of isValid() == true index.

            from PyQt5 import QtCore, QtGui
            from PyQt5.QtCore import Qt, QSize, QAbstractItemModel, pyqtSlot, pyqtSignal, QModelIndex
            from PyQt5.QtWidgets import QWidget, QMainWindow, QTextEdit, QDockWidget, QToolBar, QTabWidget, QMenu, QAction, QLayout, QTabBar, QMenu, QToolButton, QTableView, QHeaderView, QHBoxLayout, QVBoxLayout, QGridLayout, QLabel, QSplitter, QLineEdit, QSpacerItem, QSizePolicy, QPushButton, QComboBox, QDateEdit, QTreeWidget, QTreeWidgetItem, QGroupBox, QTreeView, QSpinBox, QCheckBox, QDoubleSpinBox, QRadioButton, QDateEdit, QTimeEdit, QDateTimeEdit
            import PyQt5.QtWidgets                                                                         
            from PyQt5.QtGui import QIcon, QPixmap, QStandardItemModel, QStandardItem, QPalette, QColor, QIntValidator, QDoubleValidator
            import _pickle as cPickle
            
            _DOCK_OPTS = PyQt5.QtWidgets.QMainWindow.AllowNestedDocks
            _DOCK_OPTS |= PyQt5.QtWidgets.QMainWindow.AllowTabbedDocks
            
             
            class TreeModel(QStandardItemModel):
                def __init__(self):
                    QStandardItemModel.__init__(self)
                    
                def supportedDropActions(self):
                    return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction 
            
                def mimeData(self, indexes):
                    mimedata = QtCore.QMimeData()
                    #mimedata.setData('text/xml', str(self.nodeFromIndex(indexes[0])))
                    #print("xxx mimeData = " + str(mimeData))
                    print("Starting mimeData....")
                    print("Index:    " + str(indexes))
                    print("------")
                    print(str(self.nodeFromIndex(indexes[0])))
                    print("------2")
                    node = self.nodeFromIndex(indexes[0])
                    print("Continuing mimeData....")
                    #print("Node:   " + str(node.data()))
                    #mimedata.setData('text/xml', str(node))
                    #bstream = cPickle.dumps(node)
                    #mimedata.setData('bstream', bstream)
                    print("Continuing mimeData 2....")
                    mimedata.setData('text/xml', 'mimeData')
                    return mimedata
            
                def nodeFromIndex(self, index):
                    print("Index2:    " + str(index))      
                    ip = None
                    if index.isValid() == True:
                        ip = index.internalPointer()
                        print("isValid...")
                        print(str(ip))
                    else:
                        print(str(self.root))
                        ip = self.root
                    print("before return")
                    return ip
                    ##return index.internalPointer() if index.isValid() else self.root
                    
            
                def mimeTypes(self):
                    return ['text/xml']
            
                #def mimeData(self, indexes):
                    #mimedata = QtCore.QMimeData()
                    #mimedata.setData('text/xml', 'mimeData')
                    #return mimedata
            
                def dropMimeData(self, data, action, row, column, parent):
                    print('dropMimeData %s %s %s %s' % (data.data('text/xml'), action, row, parent))
                    return True
                
                def flags(self, index):
                    if not index.isValid():
                        return QtCore.Qt.ItemIsEnabled
                    return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
            
            # -----------------------------------------------------------------------------------------------------------------------------------    
            
            class Window(QMainWindow):
                selIdxsSignal = pyqtSignal(QModelIndex)
                
                def __init__(self):
                    QMainWindow.__init__(self)
                    self.setWindowTitle("Test")
                    self.resize(600,500)
            
                    self.tb = self.addToolBar("Test") 
                    self.ShowTitleBars = False
                    self.setWidth = 1600
                    secondQMainWindow = QMainWindow()
                    self.detailWidget = QWidget()
                    self.detailLayout = QVBoxLayout()
                    self.detailWidget.setLayout(self.detailLayout)
            
                    self.detailData = (    # Database-like test data            
                        (1,       -1,     20,   'GroupA',       'Group A'      ,400,   0,    '',    False,   8,   '',   '',    0),
                        (3,        1,      3,   'ItemA1',       'Item A1'      , 50,   0,    '',    False,   8,   '',   '',    0),
                        (4,        1,      1,   'ItemA2',       'Item A2'      ,  0,   0,    '',    True,    8,   '',   '',    0),
                        (5,        1,      8,   'ItemA3',       'Item A3'      , 90,   0,    '',    False,   8,   '',   '',    0),
                        (6,        1,     10,   'ItemA4',       'Item A4'      ,120,   0,    '',    False,   8,   '',   '',    0),
                        (7,        1,      4,   'ItemA5',       'Item A5'      , 70,   0,    ' kg', True,    8,   '',   '',    2),
                        (8,       -1,     20,   'GroupB',       'Group B'      ,  0,   0,    '',    False,   8,   '',   '',    0),
                        (9,        8,      7,   'ItemB1',       'Item B1'      ,  0,   0,    '',    True,    8,   '',   '',    0),
                        (10,       8,      2,   'ItemB2',       'Item B2'      ,  0,   66,   '',    True,    8,   '',   '',    0),
                        )
                    
                    self.sourceData = (    # Database-like test data 8,       '',        '',           0),
                        (1,       -1,      1,   'Item1',   'Item 1',     0,     0,    '',    False,     8,    '',     '',    0),
                        (2,       -1,      2,   'Item2',   'Item 2',     0,     0,    '',    False,     8,    '',     '',    0),
                        (3,       -1,      3,   'Item3',   'Item 3',     0,     0,    '',    False,     8,    '',     '',    0),
                        (4,       -1,      4,   'Item4',   'Item 4',     0,     0,    '',    False,     8,    '',     '',    0),
                        (5,       -1,      5,   'Item5',   'Item 5',     0,     0,    '',    False,     8,    '',     '',    0),
                        (6,       -1,      6,   'Item6',   'Item 6',     0,     0,    '',    False,     8,    '',     '',    0),
                        (7,       -1,      7,   'Item7',   'Item 7',     0,     0,    '',    False,     8,    '',     '',    0),
                        (8,       -1,      8,   'Item8',   'Item 8',     0,     0,    '',    False,     8,    '',     '',    0),
                        (9,       -1,      9,   'Item9',   'Item 9',     0,     0,    '',    False,     8,    '',     '',    0),
                        (10,      -1,     10,   'Item10',  'Item 10',    0,     0,    '',    False,     8,    '',     '',    0),
                        (11,      -1,     11,   'Item11',  'Item 11',    0,     0,    '',    False,     8,    '',     '',    0),
                        (12,      -1,     12,   'Item12',  'Item 12',    0,     0,    '',    False,     8,    '',     '',    0),
                        (20,      -1,     20,   'Item13',  'Item 13',    0,     0,    '',    False,     8,    '',     '',    0),
                        (21,      -1,     21,   'Item14',  'Item 14',    0,     0,    '',    False,     8,    '',     '',    0),      
                        )
             
                    self.setTabPosition (Qt.LeftDockWidgetArea, QTabWidget.North)
                    self.central = secondQMainWindow
                    self.setDockOptions(_DOCK_OPTS)   #.dockNestingEnabled(True)
                    self.dw1 = QDockWidget("Pre-set items")
                    self.dw1.topLevelChanged.connect(self.HideTitleBar)
                    self.dw1.setTitleBarWidget(QWidget(self.dw1))
            
            
                    srcTreeView = QTreeView()
                    srcTreeView.setIconSize(QSize(24,24))
                    srcTreeView.setHeaderHidden(True)   
                    srcTreeView.setDragEnabled(True)
                    self.srcModel = TreeModel()
                    srcroot = self.srcModel.invisibleRootItem()
                    srcroot1 = QStandardItem("Group 1")
                    srcroot1.setIcon(QIcon(QPixmap('detail.png')))
                    srcroot.appendRow(srcroot1)
                    srcroot2 = QStandardItem("Group 2")
                    srcroot2.setIcon(QIcon(QPixmap('detail.png')))
                    srcroot.appendRow(srcroot2)
                    srcroot3 = QStandardItem("Group 3")
                    srcroot3.setIcon(QIcon(QPixmap('detail.png')))
                    srcroot.appendRow(srcroot3)
                    for ID, ParentID, Type, Name, Label, Width, Height, Units, IsEditable, FontSize, FontVariant, NumberFormat, DecimalPlaces in self.sourceData:  # for each record, generate an item in a treeModel, used in QTreeView
                        print("ID = " + str(ID))
                        print("Name = " + Name)
                        it = QStandardItem(Name)
                        it.setData(ID) #, Name)
                        it.setIcon(QIcon(self.getIconByType(Type)))
                        if ParentID == -1:
                            srcroot1.appendRow(it)
                        for item in self.iterItems(srcroot):
                            #print(item.text())
                            if item.data() == ParentID:
                                item.appendRow(it)
                    srcTreeView.setModel(self.srcModel)
                    self.dw1.setWidget(srcTreeView)
                    srcTreeView.expandAll()
                    srcTreeView.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)
                   
                    
                    SourceTree = QTreeView()
                            
                    self.addDockWidget(Qt.LeftDockWidgetArea, self.dw1)
            
                           
                    self.dw4 = QDockWidget("Group Editor")
                    self.dw4.topLevelChanged.connect(self.HideTitleBar)
                    self.dw4.setTitleBarWidget(QWidget(self.dw4))        
                    DetailW = QWidget()
                    DetailW.setAcceptDrops(True)
                    DetailSplitter = QSplitter()
                    self.DetailTree = QTreeView()  
                    self.DetailTree.setDragEnabled(True)
                    self.treeModel = TreeModel()
                    self.DetailTree.setHeaderHidden(True)   
                    self.DetailTree.setAcceptDrops(True)
                    self.DetailTree.setIconSize(QSize(24,24))
                    DetailSplitter.addWidget(self.DetailTree)
                    DetailSplitter.addWidget(self.detailWidget)
                    self.dw4.setWidget(DetailSplitter)
                    self.DetailTree.setObjectName = "DetailEditor"
                    print("Check model:   " + str(self.DetailTree.model))
            
                    
                    root = self.treeModel.invisibleRootItem()
                    root1 = QStandardItem("Master Group")
                    root1.setIcon(QIcon(QPixmap('detail.png')))
                    root.appendRow(root1)
                    for ID, ParentID, Type, Name, Label, Width, Height, Units, IsEditable, FontSize, FontVariant, NumberFormat, DecimalPlaces in self.detailData:  # for each record, generate an item in a treeModel, used in QTreeView
                        print("ID = " + str(ID))
                        print("Name = " + Name)
                        it = QStandardItem(Name)
                        it.setData(ID) #, Name)
                        it.setIcon(QIcon(self.getIconByType(Type)))
                        if ParentID == -1:
                            root1.appendRow(it)
                        for item in self.iterItems(root):
                            #print(item.text())
                            if item.data() == ParentID:
                                item.appendRow(it)
             
                    self.DetailTree.setModel(self.treeModel)
                    self.DetailTree.expandAll()
                    self.DetailTree.selectionModel().selectionChanged.connect(self.selIdxSignalChangeHandler)  
                    self.selIdxsSignal.connect(self.selectionChangeHandler) 
                            
                    
                    DetailGB1 = QGroupBox()
                    DetailGB1.setMaximumWidth(300)
                    self.addDockWidget(Qt.RightDockWidgetArea, self.dw4)    
                  
            
                    btn = QAction("A button",self)
                    self.tb.addAction(btn)
                                            
                    barTog = QAction(QIcon("arrange_24x24.png"),"Toggle Title Bars",self)
                    barTog.setCheckable(True)
                    barTog.triggered.connect(self.ToogleTitles2)
                    self.tb.addAction(barTog)
                    
                    for widget in self.children():
                        if isinstance(widget, QTabBar):
                            widget.setTabsClosable(True)
                            widget.setCurrentIndex(0)
                            break
                    
            
                def selIdxSignalChangeHandler(self):
                    self.selIdxsSignal.emit(self.DetailTree.selectionModel().selectedIndexes()[0])
                    
                def selectionChangeHandler(self, Idx):
                    print(str(Idx))
                    IID = Idx.data(Qt.UserRole + 1)
                    print("    Selection changed - IID = " + str(IID))
                    tbName = self.findChild(QLineEdit, "txtText")
                    tbWidth = self.findChild(QLineEdit, "txtWidth")
                    tbHeight = self.findChild(QLineEdit, "txtHeight")
                    tbDataType = self.findChild(QLineEdit, "txtType")
                    tbUnit = self.findChild(QLineEdit, "txtUnit")
                    tbDecimals = self.findChild(QLineEdit, "txtDecimals")
                    row = self.getRowByIndex(Idx)
                    if IID != None:
                        print("Assigning properties")
                        #tbName.setText(str(row[3]))
                        #tbWidth.setText(str(row[5]))
                        #tbHeight.setText(str(row[6]))
                        #tbDataType.setText(str(row[2]))
                        #tbUnit.setText(str(row[7]))
                        #tbDecimals.setText(str(row[12]))
            
                def getIconByType(self, Type):
                    iconList = [
                               (1, "type1.png"),
                               (3, "type2.png"),
                               (2, "type3.png"),
                               (4, "type4.png"),
                               (5, "type5.png"),
                               (6, "type6.png"),
                               (7, "type7.png"),
                               (8, "type8.png"),
                               (9, "type9.png"),
                               (10, "type10.png"),
                               (11, "type11.png"),
                               (12, "type12.png"),
                               (13, "type13.png"),
                               (14, "type14.png"),
                               (15, "type15.png"),
                               (16, "type16.png")
                               ]
                    for TypeID, Icon in iconList:
                        if TypeID == Type:
                            return Icon
                            break
                
                def getRowByIndex(self, Index):
                    model = self.treeModel
                    print("### getRowByIndex")
                    SelID = model.data(Index, Qt.UserRole+1)
                    print("### Selected ID = " + str(SelID))
                    row = None
                    for i, ir in enumerate(self.detailData):
                        if ir[0] == SelID:
                            row = self.detailData[i]
                            break
                    return row
            
                       
                def iterItems(self, root):    # get all items, credits to @ekhumuro answer
                    def recurse(parent):
                        for row in range(parent.rowCount()):
                            for column in range(parent.columnCount()):
                                child = parent.child(row, column)
                                yield child
                                if child.hasChildren():
                                    yield from recurse(child)
                    if root is not None:
                        yield from recurse(root) 
             
                def dropMimeData(self, mimedata, action, row, column, parentIndex):
                    if action == Qt.IgnoreAction: 
                        return True
                    dragNode = mimedata.instance() 
                    parentNode = self.nodeFromIndex(parentIndex) 
                    newNode = deepcopy(dragNode) #<------ why copy? Why not just reparent?
                    newNode.setParent(parentNode) 
                    self.insertRow(len(parentNode)-1, parentIndex) 
                    self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex) 
                    print("Done mime data")
                    return True 
            
                    
                def CreateNewTab(self):
                    WNoStr = str(self.CountDockWidgets())
                    dw = QDockWidget(WNoStr)
                    dw.topLevelChanged.connect(self.HideTitleBar)
                    dw.setTitleBarWidget(QWidget(dw))
                    textArea = QTextEdit()
                    textArea.setText("Text area " + WNoStr)
                    dw.setWidget(textArea)
                    dw.topLevelChanged.connect(self.HideTitleBar)
                    self.addDockWidget(Qt.LeftDockWidgetArea, dw)
                    self.tabifyDockWidget(self.dw1, dw)        
                            
                def HideTitleBar(self):
                    dockw = self.sender()
                    if dockw.isFloating() == False and self.ShowTitleBars == False:
                        dockw.setTitleBarWidget(QWidget(dockw))
            
                def ToogleTitles2(self):        
                    if self.ShowTitleBars == True:
                        self.ShowTitleBars = False
                    else:
                        self.ShowTitleBars = True
                    for widget in self.children():
                        if isinstance(widget, QDockWidget):
                            if widget.titleBarWidget() == None and self.ShowTitleBars == False:
                                if widget.isFloating() == False:
                                    widget.setTitleBarWidget(QWidget(widget))                        
                            else:
                                widget.setTitleBarWidget(None)              
                    print("Test Toggle")
                   
                    
                def SelectionEvent(self, selIdxs):
                    print("Selection changed!!!!!!!!!!!!!!!")
            
                @pyqtSlot("QModelIndex", "QModelIndex", "QVector<int>")
                def CurrentItemChanged(self, tep_left, bottom_right, roles):
                    print("CurrentItemChanged")
                    snd = self.sender()
                    print("Sender = " + str(snd) + "         " + str(snd.objectName()))
                    print("Sender Data = " + str(snd.data))
                    print("Sender Data = " + str(tep_left.data))
                    getRowByIndex(top_left)   # <<< based on index, do stuff
                    #print("Selected iems = " + str(selItems))
                
                def DroppedItem(self):
                    print("Item dropped...")
                     
                        
            # ----------------------------------------------------------------------------------------------------------------------------------------------------------------            
                                   
            if __name__ == '__main__':
                import sys
                app = PyQt5.QtWidgets.QApplication(sys.argv)
                window = Window()
                window.show()
                app.exec_()
            

            I don't think I can overcome it on my own, having basically no working example of what I'm trying to achieve, so I would like to ask the community here for advice.

            JonBJ Offline
            JonBJ Offline
            JonB
            wrote on last edited by
            #5

            @Oak77
            My suggestion: if it were me looking to figure this out or ask for help, I would reduce the size of your program and its vast number of imports down to something minimal for myself/others to look at.

            Oak77O 1 Reply Last reply
            1
            • JonBJ JonB

              @Oak77
              My suggestion: if it were me looking to figure this out or ask for help, I would reduce the size of your program and its vast number of imports down to something minimal for myself/others to look at.

              Oak77O Offline
              Oak77O Offline
              Oak77
              wrote on last edited by
              #6

              @JonB Well, it's dramatically reduced already to what I thought was close to minimum, however I'll try to review it again. I do see few bits I can get rid off. But obviously, I need to keep two tree views and some basic stuff to make it work.

              1 Reply Last reply
              0
              • Oak77O Offline
                Oak77O Offline
                Oak77
                wrote on last edited by
                #7

                OK, I went through imports and through the code and hopefully it's now very close to minimal.

                from PyQt5 import QtCore, QtGui
                from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal, QModelIndex
                from PyQt5.QtWidgets import QWidget, QMainWindow, QDockWidget, QToolBar, QAction, QVBoxLayout, QSplitter, QSizePolicy, QTreeView, QTabWidget
                import PyQt5.QtWidgets                                                                         
                from PyQt5.QtGui import QStandardItemModel, QStandardItem
                
                _DOCK_OPTS = PyQt5.QtWidgets.QMainWindow.AllowNestedDocks
                _DOCK_OPTS |= PyQt5.QtWidgets.QMainWindow.AllowTabbedDocks
                
                 
                class TreeModel(QStandardItemModel):
                    def __init__(self):
                        QStandardItemModel.__init__(self)
                        
                    def supportedDropActions(self):
                        return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction 
                
                    def mimeData(self, indexes):
                        mimedata = QtCore.QMimeData()
                        #mimedata.setData('text/xml', str(self.nodeFromIndex(indexes[0])))
                        #print("xxx mimeData = " + str(mimeData))
                        print("Starting mimeData....")
                        print("Index:    " + str(indexes))
                        print("------")
                        print(str(self.nodeFromIndex(indexes[0])))
                        print("------2")
                        node = self.nodeFromIndex(indexes[0])
                        print("Continuing mimeData....")
                        #print("Node:   " + str(node.data()))
                        #mimedata.setData('text/xml', str(node))
                        #mimedata.setData('bstream', bstream)
                        print("Continuing mimeData 2....")
                        mimedata.setData('text/xml', 'mimeData')
                        return mimedata
                
                    def nodeFromIndex(self, index):
                        print("Index2:    " + str(index))      
                        ip = None
                        if index.isValid() == True:
                            ip = index.internalPointer()
                            print("isValid...")
                            print(str(ip))
                        else:
                            print(str(self.root))
                            ip = self.root
                        print("before return")
                        return ip
                        ##return index.internalPointer() if index.isValid() else self.root
                        
                
                    def mimeTypes(self):
                        return ['text/xml']
                
                
                    def dropMimeData(self, data, action, row, column, parent):
                        print('dropMimeData %s %s %s %s' % (data.data('text/xml'), action, row, parent))
                        return True
                    
                    def flags(self, index):
                        if not index.isValid():
                            return QtCore.Qt.ItemIsEnabled
                        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
                
                # -----------------------------------------------------------------------------------------------------------------------------------    
                
                class Window(QMainWindow):
                    selIdxsSignal = pyqtSignal(QModelIndex)
                    
                    def __init__(self):
                        QMainWindow.__init__(self)
                        self.setWindowTitle("Test")
                        self.resize(600,500)
                
                        self.tb = self.addToolBar("Test") 
                        self.ShowTitleBars = False
                        self.setWidth = 1600
                        secondQMainWindow = QMainWindow()
                        self.detailWidget = QWidget()
                        self.detailLayout = QVBoxLayout()
                        self.detailWidget.setLayout(self.detailLayout)
                
                        self.detailData = (    # Database-like test data            
                            (1,       -1,     20,   'GroupA',       'Group A'      ,400,   0,    '',    False,   8,   '',   '',    0),
                            (3,        1,      3,   'ItemA1',       'Item A1'      , 50,   0,    '',    False,   8,   '',   '',    0),
                            (4,        1,      1,   'ItemA2',       'Item A2'      ,  0,   0,    '',    True,    8,   '',   '',    0),
                            (5,        1,      8,   'ItemA3',       'Item A3'      , 90,   0,    '',    False,   8,   '',   '',    0),
                            (6,        1,     10,   'ItemA4',       'Item A4'      ,120,   0,    '',    False,   8,   '',   '',    0),
                            (7,        1,      4,   'ItemA5',       'Item A5'      , 70,   0,    ' kg', True,    8,   '',   '',    2),
                            (8,       -1,     20,   'GroupB',       'Group B'      ,  0,   0,    '',    False,   8,   '',   '',    0),
                            (9,        8,      7,   'ItemB1',       'Item B1'      ,  0,   0,    '',    True,    8,   '',   '',    0),
                            (10,       8,      2,   'ItemB2',       'Item B2'      ,  0,   66,   '',    True,    8,   '',   '',    0),
                            )
                        
                        self.sourceData = (    # Database-like test data 
                            (1,       -1,      1,   'Item1',   'Item 1',     0,     0,    '',    False,     8,    '',     '',    0),
                            (2,       -1,      2,   'Item2',   'Item 2',     0,     0,    '',    False,     8,    '',     '',    0),
                            (3,       -1,      3,   'Item3',   'Item 3',     0,     0,    '',    False,     8,    '',     '',    0),
                            (4,       -1,      4,   'Item4',   'Item 4',     0,     0,    '',    False,     8,    '',     '',    0),
                            (5,       -1,      5,   'Item5',   'Item 5',     0,     0,    '',    False,     8,    '',     '',    0),
                            (6,       -1,      6,   'Item6',   'Item 6',     0,     0,    '',    False,     8,    '',     '',    0),
                            (7,       -1,      7,   'Item7',   'Item 7',     0,     0,    '',    False,     8,    '',     '',    0),
                            (8,       -1,      8,   'Item8',   'Item 8',     0,     0,    '',    False,     8,    '',     '',    0),
                            (9,       -1,      9,   'Item9',   'Item 9',     0,     0,    '',    False,     8,    '',     '',    0),
                            (10,      -1,     10,   'Item10',  'Item 10',    0,     0,    '',    False,     8,    '',     '',    0),
                            (11,      -1,     11,   'Item11',  'Item 11',    0,     0,    '',    False,     8,    '',     '',    0),
                            (12,      -1,     12,   'Item12',  'Item 12',    0,     0,    '',    False,     8,    '',     '',    0),
                            (20,      -1,     20,   'Item13',  'Item 13',    0,     0,    '',    False,     8,    '',     '',    0),
                            (21,      -1,     21,   'Item14',  'Item 14',    0,     0,    '',    False,     8,    '',     '',    0),      
                            )
                 
                        self.central = secondQMainWindow
                        self.setDockOptions(_DOCK_OPTS)   #.dockNestingEnabled(True)
                        self.dw1 = QDockWidget("Pre-set items")
                        self.dw1.topLevelChanged.connect(self.HideTitleBar)
                        self.dw1.setTitleBarWidget(QWidget(self.dw1))
                
                        # Right treeview
                        srcTreeView = QTreeView()
                        srcTreeView.setIconSize(QSize(24,24))
                        srcTreeView.setHeaderHidden(True)   
                        srcTreeView.setDragEnabled(True)
                        self.srcModel = TreeModel()
                        srcroot = self.srcModel.invisibleRootItem()
                        srcroot1 = QStandardItem("Group 1")
                        srcroot.appendRow(srcroot1)
                        srcroot2 = QStandardItem("Group 2")
                        srcroot.appendRow(srcroot2)
                        srcroot3 = QStandardItem("Group 3")
                        srcroot.appendRow(srcroot3)
                        for ID, ParentID, Type, Name, Label, Width, Height, Units, IsEditable, FontSize, FontVariant, NumberFormat, DecimalPlaces in self.sourceData:  # for each record, generate an item in a treeModel, used in QTreeView
                            print("ID = " + str(ID))
                            print("Name = " + Name)
                            it = QStandardItem(Name)
                            it.setData(ID)
                            if ParentID == -1:
                                srcroot1.appendRow(it)
                            for item in self.iterItems(srcroot):
                                #print(item.text())
                                if item.data() == ParentID:
                                    item.appendRow(it)
                        srcTreeView.setModel(self.srcModel)
                        self.dw1.setWidget(srcTreeView)
                        srcTreeView.expandAll()
                        srcTreeView.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)                        
                        self.addDockWidget(Qt.LeftDockWidgetArea, self.dw1)
                
                        # Left treeview        
                        self.dw4 = QDockWidget("Group Editor")
                        self.dw4.topLevelChanged.connect(self.HideTitleBar)
                        self.dw4.setTitleBarWidget(QWidget(self.dw4))        
                        DetailW = QWidget()
                        DetailW.setAcceptDrops(True)
                        DetailSplitter = QSplitter()
                        self.DetailTree = QTreeView()  
                        self.DetailTree.setDragEnabled(True)
                        self.treeModel = TreeModel()
                        self.DetailTree.setHeaderHidden(True)   
                        self.DetailTree.setAcceptDrops(True)
                        self.DetailTree.setIconSize(QSize(24,24))
                        DetailSplitter.addWidget(self.DetailTree)
                        DetailSplitter.addWidget(self.detailWidget)
                        self.dw4.setWidget(DetailSplitter)
                        self.DetailTree.setObjectName = "DetailEditor"
                        print("Check model:   " + str(self.DetailTree.model))
                
                        
                        root = self.treeModel.invisibleRootItem()
                        root1 = QStandardItem("Master Group")
                        root.appendRow(root1)
                        for ID, ParentID, Type, Name, Label, Width, Height, Units, IsEditable, FontSize, FontVariant, NumberFormat, DecimalPlaces in self.detailData:  # for each record, generate an item in a treeModel, used in QTreeView
                            print("ID = " + str(ID))
                            print("Name = " + Name)
                            it = QStandardItem(Name)
                            it.setData(ID) 
                            if ParentID == -1:
                                root1.appendRow(it)
                            for item in self.iterItems(root):
                                #print(item.text())
                                if item.data() == ParentID:
                                    item.appendRow(it)
                 
                        self.DetailTree.setModel(self.treeModel)
                        self.DetailTree.expandAll()
                        self.DetailTree.selectionModel().selectionChanged.connect(self.selIdxSignalChangeHandler)  
                        self.selIdxsSignal.connect(self.selectionChangeHandler) 
                                
                        self.addDockWidget(Qt.RightDockWidgetArea, self.dw4)    
                      
                        btn = QAction("A button",self)
                        self.tb.addAction(btn)
                          
                
                    def selIdxSignalChangeHandler(self):
                        self.selIdxsSignal.emit(self.DetailTree.selectionModel().selectedIndexes()[0])
                        
                    def selectionChangeHandler(self, Idx):
                        print(str(Idx))
                        IID = Idx.data(Qt.UserRole + 1)
                        print("    Selection changed - IID = " + str(IID))
                        tbName = self.findChild(QLineEdit, "txtText")
                        tbWidth = self.findChild(QLineEdit, "txtWidth")
                        tbHeight = self.findChild(QLineEdit, "txtHeight")
                        tbDataType = self.findChild(QLineEdit, "txtType")
                        tbUnit = self.findChild(QLineEdit, "txtUnit")
                        tbDecimals = self.findChild(QLineEdit, "txtDecimals")
                        row = self.getRowByIndex(Idx)
                        if IID != None:
                            print("Assigning properties")
                            #tbName.setText(str(row[3]))
                            #tbWidth.setText(str(row[5]))
                            #tbHeight.setText(str(row[6]))
                            #tbDataType.setText(str(row[2]))
                            #tbUnit.setText(str(row[7]))
                            #tbDecimals.setText(str(row[12]))
                
                    
                    def getRowByIndex(self, Index):
                        model = self.treeModel
                        print("### getRowByIndex")
                        SelID = model.data(Index, Qt.UserRole+1)
                        print("### Selected ID = " + str(SelID))
                        row = None
                        for i, ir in enumerate(self.detailData):
                            if ir[0] == SelID:
                                row = self.detailData[i]
                                break
                        return row
                
                           
                    def iterItems(self, root):    # get all items, credits to @ekhumuro answer
                        def recurse(parent):
                            for row in range(parent.rowCount()):
                                for column in range(parent.columnCount()):
                                    child = parent.child(row, column)
                                    yield child
                                    if child.hasChildren():
                                        yield from recurse(child)
                        if root is not None:
                            yield from recurse(root) 
                 
                    #def dropMimeData(self, mimedata, action, row, column, parentIndex):
                        #if action == Qt.IgnoreAction: 
                            #return True
                        #dragNode = mimedata.instance() 
                        #parentNode = self.nodeFromIndex(parentIndex) 
                        #newNode = deepcopy(dragNode) #<------ why copy? Why not just reparent?
                        #newNode.setParent(parentNode) 
                        #self.insertRow(len(parentNode)-1, parentIndex) 
                        #self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex) 
                        #print("Done mime data")
                        #return True 
                     
                                
                    def HideTitleBar(self):
                        dockw = self.sender()
                        if dockw.isFloating() == False and self.ShowTitleBars == False:
                            dockw.setTitleBarWidget(QWidget(dockw))
                       
                         
                            
                # ----------------------------------------------------------------------------------------------------------------------------------------------------------------            
                                       
                if __name__ == '__main__':
                    import sys
                    app = PyQt5.QtWidgets.QApplication(sys.argv)
                    window = Window()
                    window.show()
                    app.exec_()
                        
                
                JonBJ 1 Reply Last reply
                0
                • Oak77O Oak77

                  OK, I went through imports and through the code and hopefully it's now very close to minimal.

                  from PyQt5 import QtCore, QtGui
                  from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal, QModelIndex
                  from PyQt5.QtWidgets import QWidget, QMainWindow, QDockWidget, QToolBar, QAction, QVBoxLayout, QSplitter, QSizePolicy, QTreeView, QTabWidget
                  import PyQt5.QtWidgets                                                                         
                  from PyQt5.QtGui import QStandardItemModel, QStandardItem
                  
                  _DOCK_OPTS = PyQt5.QtWidgets.QMainWindow.AllowNestedDocks
                  _DOCK_OPTS |= PyQt5.QtWidgets.QMainWindow.AllowTabbedDocks
                  
                   
                  class TreeModel(QStandardItemModel):
                      def __init__(self):
                          QStandardItemModel.__init__(self)
                          
                      def supportedDropActions(self):
                          return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction 
                  
                      def mimeData(self, indexes):
                          mimedata = QtCore.QMimeData()
                          #mimedata.setData('text/xml', str(self.nodeFromIndex(indexes[0])))
                          #print("xxx mimeData = " + str(mimeData))
                          print("Starting mimeData....")
                          print("Index:    " + str(indexes))
                          print("------")
                          print(str(self.nodeFromIndex(indexes[0])))
                          print("------2")
                          node = self.nodeFromIndex(indexes[0])
                          print("Continuing mimeData....")
                          #print("Node:   " + str(node.data()))
                          #mimedata.setData('text/xml', str(node))
                          #mimedata.setData('bstream', bstream)
                          print("Continuing mimeData 2....")
                          mimedata.setData('text/xml', 'mimeData')
                          return mimedata
                  
                      def nodeFromIndex(self, index):
                          print("Index2:    " + str(index))      
                          ip = None
                          if index.isValid() == True:
                              ip = index.internalPointer()
                              print("isValid...")
                              print(str(ip))
                          else:
                              print(str(self.root))
                              ip = self.root
                          print("before return")
                          return ip
                          ##return index.internalPointer() if index.isValid() else self.root
                          
                  
                      def mimeTypes(self):
                          return ['text/xml']
                  
                  
                      def dropMimeData(self, data, action, row, column, parent):
                          print('dropMimeData %s %s %s %s' % (data.data('text/xml'), action, row, parent))
                          return True
                      
                      def flags(self, index):
                          if not index.isValid():
                              return QtCore.Qt.ItemIsEnabled
                          return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
                  
                  # -----------------------------------------------------------------------------------------------------------------------------------    
                  
                  class Window(QMainWindow):
                      selIdxsSignal = pyqtSignal(QModelIndex)
                      
                      def __init__(self):
                          QMainWindow.__init__(self)
                          self.setWindowTitle("Test")
                          self.resize(600,500)
                  
                          self.tb = self.addToolBar("Test") 
                          self.ShowTitleBars = False
                          self.setWidth = 1600
                          secondQMainWindow = QMainWindow()
                          self.detailWidget = QWidget()
                          self.detailLayout = QVBoxLayout()
                          self.detailWidget.setLayout(self.detailLayout)
                  
                          self.detailData = (    # Database-like test data            
                              (1,       -1,     20,   'GroupA',       'Group A'      ,400,   0,    '',    False,   8,   '',   '',    0),
                              (3,        1,      3,   'ItemA1',       'Item A1'      , 50,   0,    '',    False,   8,   '',   '',    0),
                              (4,        1,      1,   'ItemA2',       'Item A2'      ,  0,   0,    '',    True,    8,   '',   '',    0),
                              (5,        1,      8,   'ItemA3',       'Item A3'      , 90,   0,    '',    False,   8,   '',   '',    0),
                              (6,        1,     10,   'ItemA4',       'Item A4'      ,120,   0,    '',    False,   8,   '',   '',    0),
                              (7,        1,      4,   'ItemA5',       'Item A5'      , 70,   0,    ' kg', True,    8,   '',   '',    2),
                              (8,       -1,     20,   'GroupB',       'Group B'      ,  0,   0,    '',    False,   8,   '',   '',    0),
                              (9,        8,      7,   'ItemB1',       'Item B1'      ,  0,   0,    '',    True,    8,   '',   '',    0),
                              (10,       8,      2,   'ItemB2',       'Item B2'      ,  0,   66,   '',    True,    8,   '',   '',    0),
                              )
                          
                          self.sourceData = (    # Database-like test data 
                              (1,       -1,      1,   'Item1',   'Item 1',     0,     0,    '',    False,     8,    '',     '',    0),
                              (2,       -1,      2,   'Item2',   'Item 2',     0,     0,    '',    False,     8,    '',     '',    0),
                              (3,       -1,      3,   'Item3',   'Item 3',     0,     0,    '',    False,     8,    '',     '',    0),
                              (4,       -1,      4,   'Item4',   'Item 4',     0,     0,    '',    False,     8,    '',     '',    0),
                              (5,       -1,      5,   'Item5',   'Item 5',     0,     0,    '',    False,     8,    '',     '',    0),
                              (6,       -1,      6,   'Item6',   'Item 6',     0,     0,    '',    False,     8,    '',     '',    0),
                              (7,       -1,      7,   'Item7',   'Item 7',     0,     0,    '',    False,     8,    '',     '',    0),
                              (8,       -1,      8,   'Item8',   'Item 8',     0,     0,    '',    False,     8,    '',     '',    0),
                              (9,       -1,      9,   'Item9',   'Item 9',     0,     0,    '',    False,     8,    '',     '',    0),
                              (10,      -1,     10,   'Item10',  'Item 10',    0,     0,    '',    False,     8,    '',     '',    0),
                              (11,      -1,     11,   'Item11',  'Item 11',    0,     0,    '',    False,     8,    '',     '',    0),
                              (12,      -1,     12,   'Item12',  'Item 12',    0,     0,    '',    False,     8,    '',     '',    0),
                              (20,      -1,     20,   'Item13',  'Item 13',    0,     0,    '',    False,     8,    '',     '',    0),
                              (21,      -1,     21,   'Item14',  'Item 14',    0,     0,    '',    False,     8,    '',     '',    0),      
                              )
                   
                          self.central = secondQMainWindow
                          self.setDockOptions(_DOCK_OPTS)   #.dockNestingEnabled(True)
                          self.dw1 = QDockWidget("Pre-set items")
                          self.dw1.topLevelChanged.connect(self.HideTitleBar)
                          self.dw1.setTitleBarWidget(QWidget(self.dw1))
                  
                          # Right treeview
                          srcTreeView = QTreeView()
                          srcTreeView.setIconSize(QSize(24,24))
                          srcTreeView.setHeaderHidden(True)   
                          srcTreeView.setDragEnabled(True)
                          self.srcModel = TreeModel()
                          srcroot = self.srcModel.invisibleRootItem()
                          srcroot1 = QStandardItem("Group 1")
                          srcroot.appendRow(srcroot1)
                          srcroot2 = QStandardItem("Group 2")
                          srcroot.appendRow(srcroot2)
                          srcroot3 = QStandardItem("Group 3")
                          srcroot.appendRow(srcroot3)
                          for ID, ParentID, Type, Name, Label, Width, Height, Units, IsEditable, FontSize, FontVariant, NumberFormat, DecimalPlaces in self.sourceData:  # for each record, generate an item in a treeModel, used in QTreeView
                              print("ID = " + str(ID))
                              print("Name = " + Name)
                              it = QStandardItem(Name)
                              it.setData(ID)
                              if ParentID == -1:
                                  srcroot1.appendRow(it)
                              for item in self.iterItems(srcroot):
                                  #print(item.text())
                                  if item.data() == ParentID:
                                      item.appendRow(it)
                          srcTreeView.setModel(self.srcModel)
                          self.dw1.setWidget(srcTreeView)
                          srcTreeView.expandAll()
                          srcTreeView.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)                        
                          self.addDockWidget(Qt.LeftDockWidgetArea, self.dw1)
                  
                          # Left treeview        
                          self.dw4 = QDockWidget("Group Editor")
                          self.dw4.topLevelChanged.connect(self.HideTitleBar)
                          self.dw4.setTitleBarWidget(QWidget(self.dw4))        
                          DetailW = QWidget()
                          DetailW.setAcceptDrops(True)
                          DetailSplitter = QSplitter()
                          self.DetailTree = QTreeView()  
                          self.DetailTree.setDragEnabled(True)
                          self.treeModel = TreeModel()
                          self.DetailTree.setHeaderHidden(True)   
                          self.DetailTree.setAcceptDrops(True)
                          self.DetailTree.setIconSize(QSize(24,24))
                          DetailSplitter.addWidget(self.DetailTree)
                          DetailSplitter.addWidget(self.detailWidget)
                          self.dw4.setWidget(DetailSplitter)
                          self.DetailTree.setObjectName = "DetailEditor"
                          print("Check model:   " + str(self.DetailTree.model))
                  
                          
                          root = self.treeModel.invisibleRootItem()
                          root1 = QStandardItem("Master Group")
                          root.appendRow(root1)
                          for ID, ParentID, Type, Name, Label, Width, Height, Units, IsEditable, FontSize, FontVariant, NumberFormat, DecimalPlaces in self.detailData:  # for each record, generate an item in a treeModel, used in QTreeView
                              print("ID = " + str(ID))
                              print("Name = " + Name)
                              it = QStandardItem(Name)
                              it.setData(ID) 
                              if ParentID == -1:
                                  root1.appendRow(it)
                              for item in self.iterItems(root):
                                  #print(item.text())
                                  if item.data() == ParentID:
                                      item.appendRow(it)
                   
                          self.DetailTree.setModel(self.treeModel)
                          self.DetailTree.expandAll()
                          self.DetailTree.selectionModel().selectionChanged.connect(self.selIdxSignalChangeHandler)  
                          self.selIdxsSignal.connect(self.selectionChangeHandler) 
                                  
                          self.addDockWidget(Qt.RightDockWidgetArea, self.dw4)    
                        
                          btn = QAction("A button",self)
                          self.tb.addAction(btn)
                            
                  
                      def selIdxSignalChangeHandler(self):
                          self.selIdxsSignal.emit(self.DetailTree.selectionModel().selectedIndexes()[0])
                          
                      def selectionChangeHandler(self, Idx):
                          print(str(Idx))
                          IID = Idx.data(Qt.UserRole + 1)
                          print("    Selection changed - IID = " + str(IID))
                          tbName = self.findChild(QLineEdit, "txtText")
                          tbWidth = self.findChild(QLineEdit, "txtWidth")
                          tbHeight = self.findChild(QLineEdit, "txtHeight")
                          tbDataType = self.findChild(QLineEdit, "txtType")
                          tbUnit = self.findChild(QLineEdit, "txtUnit")
                          tbDecimals = self.findChild(QLineEdit, "txtDecimals")
                          row = self.getRowByIndex(Idx)
                          if IID != None:
                              print("Assigning properties")
                              #tbName.setText(str(row[3]))
                              #tbWidth.setText(str(row[5]))
                              #tbHeight.setText(str(row[6]))
                              #tbDataType.setText(str(row[2]))
                              #tbUnit.setText(str(row[7]))
                              #tbDecimals.setText(str(row[12]))
                  
                      
                      def getRowByIndex(self, Index):
                          model = self.treeModel
                          print("### getRowByIndex")
                          SelID = model.data(Index, Qt.UserRole+1)
                          print("### Selected ID = " + str(SelID))
                          row = None
                          for i, ir in enumerate(self.detailData):
                              if ir[0] == SelID:
                                  row = self.detailData[i]
                                  break
                          return row
                  
                             
                      def iterItems(self, root):    # get all items, credits to @ekhumuro answer
                          def recurse(parent):
                              for row in range(parent.rowCount()):
                                  for column in range(parent.columnCount()):
                                      child = parent.child(row, column)
                                      yield child
                                      if child.hasChildren():
                                          yield from recurse(child)
                          if root is not None:
                              yield from recurse(root) 
                   
                      #def dropMimeData(self, mimedata, action, row, column, parentIndex):
                          #if action == Qt.IgnoreAction: 
                              #return True
                          #dragNode = mimedata.instance() 
                          #parentNode = self.nodeFromIndex(parentIndex) 
                          #newNode = deepcopy(dragNode) #<------ why copy? Why not just reparent?
                          #newNode.setParent(parentNode) 
                          #self.insertRow(len(parentNode)-1, parentIndex) 
                          #self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex) 
                          #print("Done mime data")
                          #return True 
                       
                                  
                      def HideTitleBar(self):
                          dockw = self.sender()
                          if dockw.isFloating() == False and self.ShowTitleBars == False:
                              dockw.setTitleBarWidget(QWidget(dockw))
                         
                           
                              
                  # ----------------------------------------------------------------------------------------------------------------------------------------------------------------            
                                         
                  if __name__ == '__main__':
                      import sys
                      app = PyQt5.QtWidgets.QApplication(sys.argv)
                      window = Window()
                      window.show()
                      app.exec_()
                          
                  
                  JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote on last edited by JonB
                  #8

                  @Oak77
                  So, just as an example, in order to reproduce your crash, it is necessary to have dock widgets, toolbars, tabs, menus, tool buttons, icons, pixmaps, palettes, colours, validators, and Python pickling? Then you must have one of the most complex situations/problems to diagnose that I have ever heard of in a Qt program, and it must be incredibly difficult to diagnose with all those interactions contributing. Best of luck, hopefully somebody will know why these contribute to the issue and give you the solution.

                  Oak77O 1 Reply Last reply
                  1
                  • JonBJ JonB

                    @Oak77
                    So, just as an example, in order to reproduce your crash, it is necessary to have dock widgets, toolbars, tabs, menus, tool buttons, icons, pixmaps, palettes, colours, validators, and Python pickling? Then you must have one of the most complex situations/problems to diagnose that I have ever heard of in a Qt program, and it must be incredibly difficult to diagnose with all those interactions contributing. Best of luck, hopefully somebody will know why these contribute to the issue and give you the solution.

                    Oak77O Offline
                    Oak77O Offline
                    Oak77
                    wrote on last edited by
                    #9

                    @JonB said in QTreeView drag/drop handling:

                    @Oak77
                    So, just as an example, in order to reproduce your crash, it is necessary to have dock widgets, toolbars, tabs, menus, tool buttons, icons, pixmaps, palettes, colours, validators, and Python pickling?

                    I don't get it. I removed all dock widgets except the two in which the QTabTreeView sits, I removed all tabs, all icons, all tool buttons except one empty dummy, all icons, all pixmap references, all palettes, all colours, pickling, menus,...

                    Did you look into the previous code?

                    I mean you're right I could remove the (now basically empty) toolbar altogether and the QDockWidgets containers in which QTreeViews sit, but that would strip only few lines of code and didn't seem to be important. That does not affect the drag and drop part, which is basically in separate class at the beginning of the code.

                    I'm not saying I won't remove the remaining few extra objects, if you thing it's important. But from looking at your reply, it seems you just glanced at wrong code.

                    1 Reply Last reply
                    0
                    • SGaistS Offline
                      SGaistS Offline
                      SGaist
                      Lifetime Qt Champion
                      wrote on last edited by
                      #10

                      AFAIK, you need to reimplement dropMimeData and there you insert the dropped data the way you want/need in your model.

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

                      Oak77O 1 Reply Last reply
                      1
                      • SGaistS SGaist

                        AFAIK, you need to reimplement dropMimeData and there you insert the dropped data the way you want/need in your model.

                        Oak77O Offline
                        Oak77O Offline
                        Oak77
                        wrote on last edited by
                        #11

                        @SGaist Right, so I'm not doing that? Using:

                            def dropMimeData(self, data, action, row, column, parent):
                                print('dropMimeData %s %s %s %s' % (data.data('text/xml'), action, row, parent))
                                return True
                        

                        That's only printing data, I know. But if I understand it correctly, it's dependant on what's inside the mimeData and that in turn on reimplemented mimeData...(?). And that's where I'm stuck, perhaps because the selection model is so far beyond my full understanding.
                        What I'm trying to do is to retrieve the index, out of indexed row the data(), which should be an ID and get a datarow by that ID. Something I would do in VB.net using a ValueMember and thus it would be a trivial task. It's well possible I'm doing it wrong way, but I didn't find any usable examples for the job.
                        The code itself crashes on an attempt to use (or return) of index.internalPointer(). I took the original code from some examples, but they were used in QAbsstractModel, while I'm using QStandardItemModel. I'm quite lost as for how to debug this.

                        By the way, if anyone knows of a good, complete example that covers this (QTreeVeiw, QStandardItemModel /or feasible alternative/, items with value member), please just point me in that direction, I'm willing to start over :-).

                        1 Reply Last reply
                        0
                        • Oak77O Offline
                          Oak77O Offline
                          Oak77
                          wrote on last edited by
                          #12

                          I think that my problem comes from the fact, that I am following (the only available AFAIK) examples made in PyQt4 (and Python 2.6 I guess, looking at print syntaxe).

                          Here's one taken from QTreeView with drag and drop support in PyQt
                          , which I converted (or attemted, at least) to PyQt5 and Python 3.6:

                          import sys
                          from PyQt5 import QtGui, QtCore
                          from PyQt5.QtWidgets import QMainWindow, QApplication, QTreeView, QAbstractItemView
                          
                          class TreeModel(QtCore.QAbstractItemModel):
                              def __init__(self):
                                  QtCore.QAbstractItemModel.__init__(self)
                                  self.nodes = ['node0', 'node1', 'node2']
                          
                              def index(self, row, column, parent):
                                  return self.createIndex(row, column, self.nodes[row])
                          
                              def parent(self, index):
                                  return QtCore.QModelIndex()
                          
                              def rowCount(self, index):
                                  if index.internalPointer() in self.nodes:
                                      return 0
                                  return len(self.nodes)
                          
                              def columnCount(self, index):
                                  return 1
                          
                              def data(self, index, role):
                                  if role == 0: 
                                      return index.internalPointer()
                                  else:
                                      return None
                          
                              def supportedDropActions(self): 
                                  return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction         
                          
                              def flags(self, index):
                                  if not index.isValid():
                                      return QtCore.Qt.ItemIsEnabled
                                  return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
                                         QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled        
                          
                              def mimeTypes(self):
                                  return ['text/xml']
                          
                              def mimeData(self, indexes):
                                  mimedata = QtCore.QMimeData()
                                  mimedata.setData('text/xml', 'mimeData')
                                  return mimedata
                          
                              def dropMimeData(self, data, action, row, column, parent):
                                  print('dropMimeData %s %s %s %s' % (data.data('text/xml'), action, row, parent))
                                  return True
                          
                          
                          class MainForm(QMainWindow):
                              def __init__(self, parent=None):
                                  super(MainForm, self).__init__(parent)
                          
                                  self.treeModel = TreeModel()
                          
                                  self.view = QTreeView()
                                  self.view.setModel(self.treeModel)
                                  self.view.setDragDropMode(QAbstractItemView.InternalMove)
                          
                                  self.setCentralWidget(self.view)
                          
                          def main():
                              app = QApplication(sys.argv)
                              form = MainForm()
                              form.show()
                              app.exec_()
                          
                          if __name__ == '__main__':
                              main()
                          

                          It doesn't work either, with error:

                          Traceback (most recent call last):
                            File "tree_sample9.py", line 44, in mimeData
                              mimedata.setData('text/xml', 'mimeData')
                          TypeError: setData(self, str, Union[QByteArray, bytes, bytearray]): argument 2 has unexpected type 'str'
                          

                          I tried couple of different examples and got a result like that in all cases. I know it might be me messing something up the conversion, but I have no idea what's wrong.
                          Could anyone advice please, what could be wrong? And/or perhaps someone knows a good working example for Drag&Drop support in PyQt5?

                          JonBJ 1 Reply Last reply
                          0
                          • Oak77O Oak77

                            I think that my problem comes from the fact, that I am following (the only available AFAIK) examples made in PyQt4 (and Python 2.6 I guess, looking at print syntaxe).

                            Here's one taken from QTreeView with drag and drop support in PyQt
                            , which I converted (or attemted, at least) to PyQt5 and Python 3.6:

                            import sys
                            from PyQt5 import QtGui, QtCore
                            from PyQt5.QtWidgets import QMainWindow, QApplication, QTreeView, QAbstractItemView
                            
                            class TreeModel(QtCore.QAbstractItemModel):
                                def __init__(self):
                                    QtCore.QAbstractItemModel.__init__(self)
                                    self.nodes = ['node0', 'node1', 'node2']
                            
                                def index(self, row, column, parent):
                                    return self.createIndex(row, column, self.nodes[row])
                            
                                def parent(self, index):
                                    return QtCore.QModelIndex()
                            
                                def rowCount(self, index):
                                    if index.internalPointer() in self.nodes:
                                        return 0
                                    return len(self.nodes)
                            
                                def columnCount(self, index):
                                    return 1
                            
                                def data(self, index, role):
                                    if role == 0: 
                                        return index.internalPointer()
                                    else:
                                        return None
                            
                                def supportedDropActions(self): 
                                    return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction         
                            
                                def flags(self, index):
                                    if not index.isValid():
                                        return QtCore.Qt.ItemIsEnabled
                                    return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
                                           QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled        
                            
                                def mimeTypes(self):
                                    return ['text/xml']
                            
                                def mimeData(self, indexes):
                                    mimedata = QtCore.QMimeData()
                                    mimedata.setData('text/xml', 'mimeData')
                                    return mimedata
                            
                                def dropMimeData(self, data, action, row, column, parent):
                                    print('dropMimeData %s %s %s %s' % (data.data('text/xml'), action, row, parent))
                                    return True
                            
                            
                            class MainForm(QMainWindow):
                                def __init__(self, parent=None):
                                    super(MainForm, self).__init__(parent)
                            
                                    self.treeModel = TreeModel()
                            
                                    self.view = QTreeView()
                                    self.view.setModel(self.treeModel)
                                    self.view.setDragDropMode(QAbstractItemView.InternalMove)
                            
                                    self.setCentralWidget(self.view)
                            
                            def main():
                                app = QApplication(sys.argv)
                                form = MainForm()
                                form.show()
                                app.exec_()
                            
                            if __name__ == '__main__':
                                main()
                            

                            It doesn't work either, with error:

                            Traceback (most recent call last):
                              File "tree_sample9.py", line 44, in mimeData
                                mimedata.setData('text/xml', 'mimeData')
                            TypeError: setData(self, str, Union[QByteArray, bytes, bytearray]): argument 2 has unexpected type 'str'
                            

                            I tried couple of different examples and got a result like that in all cases. I know it might be me messing something up the conversion, but I have no idea what's wrong.
                            Could anyone advice please, what could be wrong? And/or perhaps someone knows a good working example for Drag&Drop support in PyQt5?

                            JonBJ Offline
                            JonBJ Offline
                            JonB
                            wrote on last edited by
                            #13

                            @Oak77 said in QTreeView drag/drop handling:

                            mimedata.setData('text/xml', 'mimeData')
                            TypeError: setData(self, str, Union[QByteArray, bytes, bytearray]): argument 2 has unexpected type 'str'
                            

                            One of (something like) the following:

                            mimedata.setData('text/xml', QtCore.QByteArray('mimeData'))
                            mimedata.setData('text/xml', QtCore.QString('mimeData').toUtf8())
                            mimedata.setData('text/xml', 'mimeData'.encode())
                            
                            Oak77O 1 Reply Last reply
                            2
                            • JonBJ JonB

                              @Oak77 said in QTreeView drag/drop handling:

                              mimedata.setData('text/xml', 'mimeData')
                              TypeError: setData(self, str, Union[QByteArray, bytes, bytearray]): argument 2 has unexpected type 'str'
                              

                              One of (something like) the following:

                              mimedata.setData('text/xml', QtCore.QByteArray('mimeData'))
                              mimedata.setData('text/xml', QtCore.QString('mimeData').toUtf8())
                              mimedata.setData('text/xml', 'mimeData'.encode())
                              
                              Oak77O Offline
                              Oak77O Offline
                              Oak77
                              wrote on last edited by
                              #14

                              @JonB said in QTreeView drag/drop handling:

                              One of (something like) the following:

                              mimedata.setData('text/xml', QtCore.QByteArray('mimeData'))
                              mimedata.setData('text/xml', QtCore.QString('mimeData').toUtf8())
                              mimedata.setData('text/xml', 'mimeData'.encode())
                              

                              Thank you very much! The very last option worked. I'm guessing I'm encoding the string into default UTF-8, but have no idea why it doesn't work without it, however now I can progress a bit and study this stuff...
                              Thank you again, that helped a lot!

                              1 Reply Last reply
                              0
                              • SGaistS Offline
                                SGaistS Offline
                                SGaist
                                Lifetime Qt Champion
                                wrote on last edited by
                                #15
                                'This is a string'
                                b'This is not a string'
                                

                                Python3 makes a strict distinction between bytes and string.

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

                                Oak77O 1 Reply Last reply
                                1
                                • SGaistS SGaist
                                  'This is a string'
                                  b'This is not a string'
                                  

                                  Python3 makes a strict distinction between bytes and string.

                                  Oak77O Offline
                                  Oak77O Offline
                                  Oak77
                                  wrote on last edited by
                                  #16

                                  @SGaist said in QTreeView drag/drop handling:

                                  'This is a string'
                                  b'This is not a string'
                                  

                                  Python3 makes a strict distinction between bytes and string.

                                  Right, but...:

                                  'mimeData is a string'.encode()
                                  

                                  ...is a string in UTF-8, correct? What I was confused about is, that string (which I'd assume is UTF-8 by default) wouldn't work until it's encoded by encode() method, which with no argument, turns the string into UTF-8 string. I don't get why it crashed before and it's fine after encoding, especially because apparently it would work in PyQt4 (I don't have PyQt4 installed, didn't check, but all the references are with plain string). Not a big deal though, just my curiosity why.

                                  JonBJ 1 Reply Last reply
                                  0
                                  • Oak77O Oak77

                                    @SGaist said in QTreeView drag/drop handling:

                                    'This is a string'
                                    b'This is not a string'
                                    

                                    Python3 makes a strict distinction between bytes and string.

                                    Right, but...:

                                    'mimeData is a string'.encode()
                                    

                                    ...is a string in UTF-8, correct? What I was confused about is, that string (which I'd assume is UTF-8 by default) wouldn't work until it's encoded by encode() method, which with no argument, turns the string into UTF-8 string. I don't get why it crashed before and it's fine after encoding, especially because apparently it would work in PyQt4 (I don't have PyQt4 installed, didn't check, but all the references are with plain string). Not a big deal though, just my curiosity why.

                                    JonBJ Offline
                                    JonBJ Offline
                                    JonB
                                    wrote on last edited by JonB
                                    #17

                                    @Oak77 said in QTreeView drag/drop handling:

                                    I don't get why it crashed before and it's fine after encoding,

                                    The error message told you that you were passing a str/QString and the method expects a QByteArray/bytes, as per the documentation. They are not the same. I can't comment about PyQt4 --- you'd have to look up changes, and Qt4/PyQt4 has changed at 5, and also Python from 2.x to 3.x if relevant --- but it's just a parameter type mismatch here, no mystery. (BTW, yes, encode() is UTF-8 default.)

                                    1 Reply Last reply
                                    1
                                    • SGaistS Offline
                                      SGaistS Offline
                                      SGaist
                                      Lifetime Qt Champion
                                      wrote on last edited by
                                      #18

                                      The encode method returns a bytes object.

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

                                      1 Reply Last reply
                                      0
                                      • Oak77O Offline
                                        Oak77O Offline
                                        Oak77
                                        wrote on last edited by
                                        #19

                                        @JonB and @SGaist

                                        Thank you for your explanation, both thanks to your comments and my 2 hours over docs and testing yesterday helped me. You might hate me though, through all that time I was unable to convert that QByteArray back to string :-). It makes me feel stupid, but looking into tons of confused threads, at least I'm not alone...

                                        b = b'MyString'
                                        

                                        ...is a QByteArray, if I'm not completely lost. Then...

                                        print(b.decode("utf-8"))
                                        

                                        ...should output (and does):

                                        MyString
                                        

                                        Then, when the same MyString string is set to data, we get:

                                        print("   1 ----  " + str(data.data('text/xml')))
                                        

                                        outputs a string:

                                        1 ----  b'MyString'
                                        

                                        And I would expect decoding it would give me a string:

                                        print("   2 ----  " + str(data.data('text/xml').decode("utf-8")))
                                        

                                        ...but, I get an error:

                                        Traceback (most recent call last):
                                          File "test4.py", line 35, in dropMimeData
                                            print("   2 ----  " + str(data.data('text/xml').decode("utf-8")))
                                        

                                        OK, there's a brute, ugly way, but I don't anyone expect to embrace it:

                                        print(str(data.data('text/xml'))[2:-1])
                                        

                                        Output is correct:

                                        MyString
                                        

                                        I did google related threads, but none related to mimeData and it works with byte arrays as shown above. Maybe I'm getting old for this stuff :-)

                                        1 Reply Last reply
                                        0
                                        • SGaistS Offline
                                          SGaistS Offline
                                          SGaist
                                          Lifetime Qt Champion
                                          wrote on last edited by
                                          #20

                                          @Oak77 said in QTreeView drag/drop handling:

                                          b = b'MyString'

                                          ...is a QByteArray, if I'm not completely lost.

                                          Do not use single letter variable. It's a easy way to break all kinds of stuff like the interaction in the debugger.

                                          That said, sorry, you are a bit lost. It's a Python bytes. The method you are calling accepts it as well as QByteArray that you would have to explicitly build. You cannot convert a bytes to a string by calling str() on it, you have to use the encode method. If you call str(your_bytes) you will get "b'MyString'".

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

                                          Oak77O 1 Reply Last reply
                                          0

                                          • Login

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