AlexandreS
AlexandreS

Reputation: 39

Model/View QListView with complex widget representation

I'm trying to display in a PyQT5 Application a QListView to display some data.

My goal is to represent those data with a complex representation, not just line of text.

So, my QListView should "instantiate" a new QWidget loaded from a Ui file, and display each elements with that Ui representation.

My issue is with the layout of my Windows.

I have a main window, in which there is a QSplitter, the QListView is on the left, and on the right there are some widgets (Label, textedit, ...) QMainWindow Designer:
QMainWindow Designer

Each line of my QListView I want to represent as this small widget: Mini Widget Designer:
Mini Widget Designer

It seems that IF the QListView is inside a Widget Container like this:

Arbo object inspectors:

Arbo object inspectors

Then I have a weird behavior where child widget are displayed on top of each others:

ListView widget stacking:

ListView widget stacking

But if I remove the container of the QListView OkWindow Designer:

OkWindow Designer

and the view of the inspector

enter image description here

They are properly displayed

Working as intented:

Working as intented

Any idea why?

Is there anything wrong with my code and the way to implement complex widget representation inside a Model/View QListView?

Here is the code of the application example:

import typing

from PyQt5 import QtCore, uic
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt
from PyQt5.QtWidgets import (
    QApplication,
    QStyledItemDelegate,
    QWidget, QMainWindow, )


class Editor(QWidget):
    def __init__(self, index, parent=None):
        super().__init__(parent)
        uic.loadUi('step.ui', self)

        value = index.model().data(index, Qt.ItemDataRole)

        self.label.setText(value)


class MyListModel(QAbstractListModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._data_list = []

    def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> typing.Any:
        if role == Qt.ItemDataRole:
            return self._data_list[index.row()]

    def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
        return len(self._data_list)

    @property
    def data_list(self):
        return self._data_list

    @data_list.setter
    def data_list(self, data_list):
        self.beginResetModel()
        self._data_list = data_list.copy()
        self.endResetModel()


class StyledItemDelegate(QStyledItemDelegate):

    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)
        self.editor = None

    def createEditor(self, parent, option, index):
        self.editor = Editor(index, parent)
        return self.editor

    def sizeHint(self, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> QtCore.QSize:
        if self.editor:
            return self.editor.sizeHint()
        else:
            return super().sizeHint(option, index)

data = ["Apple", "Strawberry", "Cherry"]


class MainWindow(QMainWindow):
    def __init__(self, qapp):
        """
        Init main window
        """
        super().__init__()

        self.qapp = qapp
        # Not working main_window ui file 
        # uic.loadUi('main_window.ui', self)

        # Working main_window ui file 
        uic.loadUi('main_window_ok.ui', self)

        self.load_data()

    def load_data(self):
        controller = MyCtrl(self)
        controller.load_datalist()

class MyCtrl:
    def __init__(self, parent: QMainWindow):
        self.parent = parent

    def load_datalist(self):
        self.model = MyListModel()
        self.model.data_list = data
        self.parent.listView.setModel(self.model)
        delegate = StyledItemDelegate(self.parent.listView)
        self.parent.listView.setItemDelegate(delegate)

        for i in range(self.model.rowCount()):
            index = self.model.index(i, 0)
            self.parent.listView.openPersistentEditor(index)


if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)
    main = MainWindow(app)
    main.showMaximized()

    sys.exit(app.exec_())

main_window.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>642</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout_3">
    <item>
     <widget class="QSplitter" name="splitter">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
      </property>
      <widget class="QWidget" name="widget" native="true">
       <layout class="QVBoxLayout" name="verticalLayout">
        <item>
         <widget class="QListView" name="listView"/>
        </item>
       </layout>
      </widget>
      <widget class="QWidget" name="widget_2" native="true">
       <layout class="QVBoxLayout" name="verticalLayout_2">
        <item>
         <widget class="QLabel" name="label">
          <property name="text">
           <string>TextLabel</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QLineEdit" name="lineEdit"/>
        </item>
        <item>
         <widget class="QSlider" name="horizontalSlider">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
         </widget>
        </item>
        <item>
         <spacer name="verticalSpacer">
          <property name="orientation">
           <enum>Qt::Vertical</enum>
          </property>
          <property name="sizeHint" stdset="0">
           <size>
            <width>20</width>
            <height>40</height>
           </size>
          </property>
         </spacer>
        </item>
       </layout>
      </widget>
     </widget>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <spacer name="horizontalSpacer">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton_2">
        <property name="text">
         <string>Cancel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton">
        <property name="text">
         <string>Ok</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>26</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

main_window_ok.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>799</width>
    <height>642</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout_3">
    <item>
     <widget class="QSplitter" name="splitter">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
      </property>
      <widget class="QListView" name="listView"/>
      <widget class="QWidget" name="widget_2" native="true">
       <layout class="QVBoxLayout" name="verticalLayout_2">
        <item>
         <widget class="QLabel" name="label">
          <property name="text">
           <string>TextLabel</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QLineEdit" name="lineEdit"/>
        </item>
        <item>
         <widget class="QSlider" name="horizontalSlider">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
         </widget>
        </item>
        <item>
         <spacer name="verticalSpacer">
          <property name="orientation">
           <enum>Qt::Vertical</enum>
          </property>
          <property name="sizeHint" stdset="0">
           <size>
            <width>20</width>
            <height>40</height>
           </size>
          </property>
         </spacer>
        </item>
       </layout>
      </widget>
     </widget>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <spacer name="horizontalSpacer">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton_2">
        <property name="text">
         <string>Cancel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton">
        <property name="text">
         <string>Ok</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>799</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

step.ui file

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>387</width>
    <height>173</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLabel" name="label">
     <property name="text">
      <string>TextLabel</string>
     </property>
    </widget>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QDial" name="dial"/>
     </item>
     <item>
      <widget class="QComboBox" name="comboBox"/>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

Upvotes: 0

Views: 1412

Answers (1)

eyllanesc
eyllanesc

Reputation: 243887

Creating a variable that stores a QWidget is useless and dangerous since for example you would only have the last widget or worse would be to access a widget that has been removed. Instead you must use a role to store and get the size, and the default role is Qt::SizeHintRole.

from PyQt5.uic import loadUi
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt, QSize
from PyQt5.QtWidgets import (
    QApplication,
    QStyledItemDelegate,
    QWidget,
    QMainWindow,
)


class Editor(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("step.ui", self)


class MyListModel(QAbstractListModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._data_list = list()

        self._size_hints = dict()

    def rowCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return len(self._data_list)

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return self._data_list[index.row()]
        elif role == Qt.SizeHintRole:
            return self._size_hints.get(index.row(), QSize(100, 30))

    def setData(self, index, value, role=Qt.EditRole):
        if role == Qt.SizeHintRole:
            self._size_hints[index.row()] = value
            self.dataChanged.emit(index, index, (role,))
            return True
        return False

    @property
    def data_list(self):
        return self._data_list

    @data_list.setter
    def data_list(self, data_list):
        self.beginResetModel()
        self._data_list = data_list.copy()
        self.endResetModel()


class StyledItemDelegate(QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        editor = Editor(parent)
        model = index.model()
        model.setData(index, editor.sizeHint(), Qt.SizeHintRole)
        return editor

    def setEditorData(self, editor, index):
        value = index.data()
        editor.label.setText(value)


data = ["Apple", "Strawberry", "Cherry"]


class MainWindow(QMainWindow):
    def __init__(self):
        """
        Init main window
        """
        super().__init__()
        loadUi("main_window.ui", self)


class MyCtrl:
    def __init__(self, view):
        self.view = view

    def load_datalist(self):
        self.model = MyListModel()
        self.model.data_list = data
        self.view.listView.setModel(self.model)
        delegate = StyledItemDelegate(self.view.listView)
        self.view.listView.setItemDelegate(delegate)

        for i in range(self.model.rowCount()):
            index = self.model.index(i, 0)
            self.view.listView.openPersistentEditor(index)


if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)

    main = MainWindow()
    main.showMaximized()

    controller = MyCtrl(main)
    controller.load_datalist()

    sys.exit(app.exec_())

Upvotes: 1

Related Questions