Partage
  • Partager sur Facebook
  • Partager sur Twitter

Changer le widget par défaut de QListView

    24 novembre 2024 à 23:45:19

    Bonjour à tous,

    Je suis sur un projet utilisant l'architecture MV de Qt avec une QListView pour afficher une liste de musiques. Actuellement la liste ressemble à l'image en bas de post. Seulement c'est moche et pas très utile pour donner des infos, j'aimerai faire un truc plus stylisé pour afficher l'image de l'album, les artistes et le chemin du ficher. 

    Sauf que je n'ai pas trouvé de moyen pour remplacer le widget par défaut qui est utilisé pour l'affichage d'un élément de la liste.

    Les deux principales réponses sur le net sont :

    • utiliser la méthode paint de QStyledItemDelegate mais ça de permet pas de remplacer le widget, juste de le styliser et ça compliquerai la tâche pour ajouter d'autres éléments (pas de QLabel ou de layout il faut tout faire à la main, et pas d'interactivité).
    • Utiliste un QListWidget mais je perd tous les avantages la QListView qui fonctionne avec l'architecture MV de Qt.
    Je suppose qu'il doit tout de même y avoir un moyen donc je tente ma chance ici, en espérant que des âmes éclairées m'aideront :)
    Salutations

    -
    Edité par Choucroute_melba 24 novembre 2024 à 23:47:07

    • Partager sur Facebook
    • Partager sur Twitter

    Quand il n'y a pas de solution c'est qu'il n'y a pas de problème.

      25 novembre 2024 à 3:40:51

      Salut,

      En fait, il faudrait commencer par créer tes propres widgets:

      un qui contiendrait les widgets te permetttant de présenter les inforamtions relatives à un seul morceau (image de l'album, le titre de l'album, le titre de la chancon, le groupe, tout ce que tu veux)

      et un qui contiendrait la liste des morceaux en elle-même, et qui rajouterait automatiquement le premier widget dont je viens de parler lorsque tu rajoute un morceau à cette play-liste.

      Tu as, a priori, deux manières d'arriver à créer ces widgets : en passant par QtDesigner, qui te permettra de voir directement la forme que prend ta création, mais qui est "plus difficile" à utiiser dans un contexte complexe (comme le fait de rajouter un widget automatiquement) ou, "simplement" en créant une bonne vieille classe en C++, qui hérite de QWidget, et dans laquelle tu rajoute "tout ce que tu veux".

      L'inconvénient de cette méthode est que tu travailleras plus ou moins "en aveugle" pour le positionnement des différentes parties, car tu ne pourras voir le résultat "effectif" qu'une fois ton code compilé.

      En outre, comme la deuxième solution consiste à écrire du code soi-même, cela pourrait bien prendre "un peu plus plus de temps" que la première :p

      En tout état de cause, il serait intéressant (surtout pour nous, si tu veux que l'on puisse t'aider efficacement) de faire une sorte de "modèle" du résultat que tu veux obtenir, quitte à ce que ce soit fait rapidement au crayon sur une feuille volante ;)

      • Partager sur Facebook
      • Partager sur Twitter
      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
        25 novembre 2024 à 13:40:58

        Hello, déjà merci pour ta réponse :)

        Si j'ai bien compris tu me proposes de créer moi-même une liste un peut comme ça ?

        #include <QWidget>
        #include <QVBoxLayout>

        #include "TracksListModel.h"
        #include "widgets/TitleWidget.h"

        class TracksListWidget : public QWidget
        {
        Q_OBJECT

        public:
        explicit TracksListWidget(QWidget *parent, TracksListModel *model) : QWidget(parent), m_model(model)
        {
        QVBoxLayout *layout = new QVBoxLayout(this);
        for (int i = 0; i < m_model->rowCount(); i++) {
        Title title = m_model->data(m_model->index(i), Qt::UserRole).value<Title>();
        // TitleWidget = le widget perso que je veux mettre dans la liste
        layout->addWidget(new TitleWidget(this, title));
        }
        }

        private:
        TracksListModel *m_model;
        };

        Sauf qu'avec cette approche je suis obligé de réimplémenter tout ce que QListView fait pour moi pour interagir avec le modèle...

        C'est là que ça pêche, pour moi la solution la plus simple aurait été de changer le widget utilisé par QListView mais apparemment Qt n'offre pas cette possibilité... A moins de bricoler ?

        Mon idée c'est de faire un truc dans ce genre, mais avec des infos différentes (toute dispo dans une ligne de TracksListModel) :

        -
        Edité par Choucroute_melba 25 novembre 2024 à 13:58:42

        • Partager sur Facebook
        • Partager sur Twitter

        Quand il n'y a pas de solution c'est qu'il n'y a pas de problème.

          25 novembre 2024 à 18:16:41

          Comme c'est des fonctionnalités directement implémentées par les contrôles Windows, Qt n'est juste qu'un wrapper ici :

          https://forum.qt.io/topic/142288/how-can-i-add-custom-widget-to-qlistview/14

          • Partager sur Facebook
          • Partager sur Twitter
          Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
            25 novembre 2024 à 23:33:50

            Je n'ai pas comprit en quoi QListWidget ne répond pas au besoin ? Surtout que la classe hérite de QListView.

            • Partager sur Facebook
            • Partager sur Twitter
              26 novembre 2024 à 0:03:04

              QListWidget est un QListView avec un modele standard en interne. C'est juste une commodité pour ne pas avoir a creer séparément la vue et le modèle. Mais c'est pareil.

              A priori, ce que tu veux faire se fait bien avec un QStyledItemDelegate (je n'ai pas utilisé les widgets depuis longtemps). Il faut juste creer un "editor" spécifique, qui sera le widget que tu veux utiliser pour chaque ligne.

              • Partager sur Facebook
              • Partager sur Twitter
                26 novembre 2024 à 15:20:36

                Salut, En effet la classe QListWidget hérite de QListView et me permet de mettre des widgets perso en faisant ça :

                TracksListModel *model = new TracksListModel();
                QListWidget *view = new QListWidget(this);
                TracksListDelegate *delegate = new TracksListDelegate(view);
                for (int i = 0; i < model->rowCount(); i++) {
                Title title = model->data(model->index(i, 0), Qt::UserRole).value<Title>();
                QListWidgetItem *item = new QListWidgetItem(view);
                view->addItem(item);
                QLabel *label = new QLabel(title.fileName);
                view->setItemWidget(item, label);
                }

                Seulement avec cette solution je ne peux pas mettre mon modèle perso (TracksListModel) donc je perd de toutes façons l'avantage de la QListView qui réagissait avec le modèle. Je ne comprend pas trop pourquoi Qt ne permet pas de changer de widget dans la View si ils l'ont fait dans la QListWidget...

                gbdivers a écrit:

                A priori, ce que tu veux faire se fait bien avec un QStyledItemDelegate (je n'ai pas utilisé les widgets depuis longtemps). Il faut juste creer un "editor" spécifique, qui sera le widget que tu veux utiliser pour chaque ligne.

                Oui mais du coup l'éditor c'est un widget pour éditer la valeur d'une ligne. Pour contrôler ce qui est affiché par défaut, c'est la méthode paint et elle ne permet pas d'utiliser les widgets et les layouts.

                Donc au final je suppose que je n'ai pas d'autres choix que de faire une sous-classe de QListView avec mon modèle ? Par contre je n'ai aucune idée de comment m'y prendre...

                -
                Edité par Choucroute_melba 26 novembre 2024 à 15:26:49

                • Partager sur Facebook
                • Partager sur Twitter

                Quand il n'y a pas de solution c'est qu'il n'y a pas de problème.

                  28 novembre 2024 à 17:23:39

                  Bon, reprenons depuis le début...

                  Attention, quand je disais "qui hérite de QWidget", en réalité, je voulais dire "qui hérite de n'importe quelle classe héritant déjà de QWidget qui soit adapté à tes besoins"...

                  Donc, pour la représentation d'un morceau particulier, ce pourrait être une classe qui hérite de QListWidgetItem et, pour la liste en elle-même, une classe qui dérive de QListWidget (si elle ne suffit pas en elle-même), voir de QListView.

                  Il n'y a -- en effet -- aucune raison de partir d'un QWidget, qui a la possibilité de tout faire, mais qui ne fait rien par lui même (ou, du moins, qui ne fait pas grand chose par lui-même), alors que des classes spécialisées dans certains traitement (comme QListWidget et QListWidgetItem) prennent "naturellement" en charge une grande partie des comportements que tu es en droit d'attendre de la part de tes classes perso ;)

                  Ceci dit, c'est ma faute : j'aurais du le préciser dés le départ.  Et pourtant, cela me paraissait tellement logique :p

                  D'ailleurs, il se peut tout à fait que les classes dont je viens de parler ne correspondent pas tout à fait à tes besoins ou à ta situation spécifique.  Qu'à cela ne tienne, tu dispose d'une liste de classes susceptible d'être utilisée comme base pour tes widgets perso des plus importantes et que tu peux consulter ==> ICI <== .  

                  Ce serait un monde si tu n'y trouvais pas une classe sur laquel tu puisse baser ton travail ;)

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                    28 novembre 2024 à 17:53:45

                    C'est justement ce que je me demandais si il n'y avait pas moyen d'hériter d'une class QListView ou autre pour afficher tes widgets persos dans la liste tout en gardant l'avantage d'une liste et d'un modèle.

                    Maintenant je ne connais pas très bien Qt. Ca fait longtemps que je n'ai plus utilisé Qt. 

                    -
                    Edité par OmbreNoire 28 novembre 2024 à 17:55:18

                    • Partager sur Facebook
                    • Partager sur Twitter
                      29 novembre 2024 à 21:42:31

                      Hello,

                      koala01 a écrit:

                      Ce serait un monde si tu n'y trouvais pas une classe sur laquel tu puisse baser ton travail ;)

                      en effet il y a de quoi faire :lol:

                      Ceci dit je ne connaît pas très bien Qt et je trouve parfois difficile de savoir quelles fonctions réimplémenter et comment.

                      Mais finalement j'ai fait une sous classe de QAbstractItemView, ça a demandé un peut de travail pour faire un truc fonctionnel (et encore pas forcément propre) et j'ai pas mal galéré à faire l'UI (sans Qt designer du coup). Je suppose que c'était le meilleur compromis que je pouvais faire, sinon j'ai aucune idée de comment m'y prendre avec une QListeView ou widget...

                      Je vous met le code en fin de post, je veux bien des retours si possible :)

                      Merci encore et bon week-end

                      TracksListView.h

                      #ifndef TRACKSLISTVIEW_H
                      #define TRACKSLISTVIEW_H
                      #include <QListWidget>
                      #include <QVBoxLayout>
                      #include <QLabel>
                      
                      #include "../core/Title.h"
                      
                      enum TracksListSort {NoSort, Name, Date, FileLocation, FileName};
                      
                      class TracksListView : public QAbstractItemView {
                          Q_OBJECT
                      
                      public:
                          explicit TracksListView(QWidget *parent = nullptr);
                          QRect visualRect(const QModelIndex &index) const override;
                          void scrollTo(const QModelIndex &index, ScrollHint hint) override;
                          QModelIndex indexAt(const QPoint &point) const override;
                          void setSortByName(const bool sort, const Qt::SortOrder order = Qt::AscendingOrder);
                      
                      protected slots:
                          void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles) override;
                          void rowsInserted(const QModelIndex &parent, int start, int end) override;
                          void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;
                      
                      protected:
                          QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
                          int horizontalOffset() const override;
                          int verticalOffset() const override;
                          bool isIndexHidden(const QModelIndex &index) const override;
                          void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) override;
                          QRegion visualRegionForSelection(const QItemSelection &selection) const override;
                      
                      private:
                          QWidget *parent;
                          QList<QWidget *> items;
                          QVBoxLayout *listLayout;
                          QWidget *emptyListWidget;
                      
                          TracksListSort sortBy = TracksListSort::NoSort;
                          Qt::SortOrder sortOrder = Qt::AscendingOrder;
                      };
                      
                      
                      
                      #endif //TRACKSLISTVIEW_H
                      

                      TracksListView.cpp

                      #include "TracksListView.h"
                      
                      #include <iostream>
                      #include <QPushButton>
                      #include <QScrollArea>
                      
                      #include "TitleWidget.h"
                      #include "../core/models/TracksListModel.h"
                      
                      TracksListView::TracksListView(QWidget *parent) : QAbstractItemView(parent), parent(parent) {
                      
                          QScrollArea *scrollArea = new QScrollArea(this);
                          scrollArea->setWidgetResizable(true);
                          QWidget *scrollAreaWidget = new QWidget(scrollArea);
                          scrollArea->setStyleSheet("background-color: white;");
                          scrollAreaWidget->setStyleSheet("background-color: white;");
                      
                          listLayout = new QVBoxLayout(scrollAreaWidget);
                          listLayout->setAlignment(Qt::AlignTop);
                          if (items.empty()) {
                              Title title = Title();
                              emptyListWidget = new QLabel("No tracks found", this);
                              listLayout->addWidget(emptyListWidget);
                          }
                          else {
                              for (QWidget *item: items) {
                                  listLayout->addWidget(item);
                              }
                          }
                      
                          scrollArea->setWidget(scrollAreaWidget);
                          QVBoxLayout *mainLayout = new QVBoxLayout(this);
                          mainLayout->addWidget(scrollArea);
                          setContentsMargins(0, 0, 0, 0);
                          setViewportMargins(0, 0, 0, 0);
                          scrollArea->setContentsMargins(0, 0, 0, 0);
                      }
                      
                      QRect TracksListView::visualRect(const QModelIndex &index) const {
                          return items[index.row()]->geometry();
                      }
                      
                      void TracksListView::scrollTo(const QModelIndex &index, ScrollHint hint) {
                      
                      }
                      
                      QModelIndex TracksListView::indexAt(const QPoint &point) const {
                          return QModelIndex();
                      }
                      
                      void TracksListView::setSortByName(const bool sort, const Qt::SortOrder order) {
                          sortBy = Name;
                          sortOrder = order;
                          this->model()->sort(0, sortOrder); // ça ça marche pas 
                      }
                      
                      
                      void TracksListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles) {
                          std::cout << "View received dataChanged from " << topLeft.row() << " to " << bottomRight.row() << std::endl;
                          try {
                              if (listLayout->indexOf(emptyListWidget) != -1) {
                                  listLayout->removeWidget(emptyListWidget);
                                  delete emptyListWidget;
                              }
                              for (int i = topLeft.row(); i <= bottomRight.row(); i++) {
                                  Title title = model()->data(model()->index(i, 0), Qt::UserRole).value<Title>();
                                  //std::cout << title.metaData.name.toStdString() << " - " << title.metaData.artists.join(", ").toStdString() << std::endl;
                                  listLayout->replaceWidget(items[i], new TitleWidget(this, title));
                                  delete items[i];
                                  items[i] = listLayout->itemAt(i)->widget();
                              }
                          } catch (QException &e) {
                              std::cerr << e.what() << std::endl;
                          }
                      }
                      
                      void TracksListView::rowsInserted(const QModelIndex &parent, int start, int end) {
                          std::cout << "View received rowsInserted" << std::endl;
                          for (int i = start; i <= end; i++) {
                              Title title = model()->data(model()->index(i, 0), Qt::UserRole).value<Title>();
                              if (i < items.size() - 1) {
                                  items.insert(i, new TitleWidget(this, title));
                                  listLayout->insertWidget(i, items[i]);
                              }
                              else {
                                  items.append(new TitleWidget(this, title));
                                  listLayout->addWidget(items[i]);
                              }
                          }
                          this->parentWidget()->resize(this->parentWidget()->size());
                      }
                      
                      void TracksListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
                          QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
                      }
                      
                      QModelIndex TracksListView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) {
                          return QModelIndex();
                      }
                      
                      int TracksListView::horizontalOffset() const {
                          return 0;
                      }
                      
                      int TracksListView::verticalOffset() const {
                          return 0;
                      }
                      
                      bool TracksListView::isIndexHidden(const QModelIndex &index) const {
                          return items[index.row()]->isHidden();
                      }
                      
                      void TracksListView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) {
                      }
                      
                      QRegion TracksListView::visualRegionForSelection(const QItemSelection &selection) const {
                          QModelIndex index = selection.indexes()[0];
                          return visualRect(index);
                      }
                      
                      
                      

                      et le code qui crée tout ça :

                      TracksListModel *model = new TracksListModel();
                      TracksListFilter *filter = new TracksListFilter();
                      filter->setSourceModel(model);
                      TracksListView *view = new TracksListView();
                      view->setModel(filter);
                      filter->sort(0, Qt::AscendingOrder);



                      • Partager sur Facebook
                      • Partager sur Twitter

                      Quand il n'y a pas de solution c'est qu'il n'y a pas de problème.

                      Changer le widget par défaut de QListView

                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                      • Editeur
                      • Markdown