Bonjour, je suis en train de construire un soft basé sur une architecture Vue / Model avec Qt
J'ai une classe ModelData et deux classe de Vue appelées ViewCycleModel et ViewSensorModel qui héritent toutes les deux d'une classe AbstractViewModel. Mes deux vues se remplissent en allant piocher certaines datas dans une même instance de ModelData partagé via un QSharedPointer<ModelData>
Le tout est managé par une QMainWindow
Je vais essayer de ne pas vous noyer dans le code en ne vous postant que le minimal pour illustrer, n'hésitez pas à me demander plus de détails si ça vous semble nécéssaire
Voilà à quoi ressemble la base de ma MainWindow
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
private:
Ui::MainWindow *ui;
QSharedPointer<ModelData> m_model {};
QList<AbstractViewModel*> m_views;
};
// COnstructeur de MainWidow
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
m_model(QSharedPointer<ModelData>(new ModelData(this))) // Model vide créé, potentiellement setup plus tard via un fichier .json
{
ui->setupUi(this);
// Les deux vues sont crées dans Qt Designer par promotion de 2 QWidget en ViewCycleModel et ViewSensorModel
m_views.append(ui->cycleView);
m_views.append(ui->sensorView);
// J'applique le model à chaque vue existante
for(auto& view : m_views)
view->setModel(m_model);
}
Du côté d'AbstractViewModel on a :
class AbstractViewModel : public QWidget
{
Q_OBJECT
public:
explicit AbstractViewModel(QWidget *parent = nullptr);
void setModel(QSharedPointer<ModelData> model);
// Je passe les détails
protected:
QSharedPointer<ModelData> m_model {};
};
Et voici l'implémentation de setModel :
void AbstractViewModel::setModel(QSharedPointer<ModelData> model)
{
if(!m_model.isNull()) { // If a model is already applied to the view
clearView(); // Je reset chaque vue avec ses valeurs par defaut
m_model.clear(); // Je reset le QSharedPointer de la vue concernée
}
m_model = model;
}
Tout fonctionne parfaitement jusque là, soit l'utilisateur ouvre un fichier .json qui va setup le model et remplir les vues, soit il laisse le model vide par défaut, change les valeurs des Widgets de chaque vue, update le model avec les valeurs de la vue et utilise ce modèle mos à jour pour enregistrer un nouveau fichier .json
Par contre dès que je ferme le programme (même si je n'ai rien fait avec, juste lancer le programme puis quitter donc rien de plus que le constructeur de MainWindow et de ses enfants) il ne se ferme pas correctement et m'affiche :
12:16:47: Le programme s'est terminé subitement.
12:16:47: The process was ended forcefully.
12:16:47: C:\Users\thibault\....\debug\ElectrodesSetter.exe crashed
J'ai trouvé que le problème venait du partage du modèle via le QSharedPointer puisqu'il me suffit de retirer les lignes suivantes du constructeur de MainWindow pour pouvoir fermer le programme convenablement, sans le message de crash
Evidemment sans ces lignes, le soft ne fonctionne plus. Je ne peux que l'ouvrir et le fermer
Quand je passe le debugguer dans le constructeur de MainWindow je retrouve bien le QSharedPointed nommé m_model dans QMainWindow, dans ViewCycleModel et dans ViewSensorModel. Leur champ "data" correspond bien à la même adresse mémoire et j'ai bien 3 strong références (voir photo)
Quand j'ouvre un fichier json pour setup le model, je vois bien le model se mettre à jour avec les bonnes valeurs dans toutes les classes l'utilisant donc jusque là j'ai l'impression que le QSharedPointer fonctionne bien comme je le veux
Mais voilà, au moment de fermer mon application (et uniquement au moment de la fermer) il y a ce message de sortie qui indique un crash, comme si la ressource n'était pas correctement libérée
J'ai conscience que la question est peut-être un petit peu vague, mais si quelqu'un pouvait m'aider à comprendre d'où sort ce crash à la fermeture du soft et ce que je fais de mal dans l'utilisation du QSharedPointer ça serait super
J'ai l'impression que tu confond la notion de modèle de la notion MVC (Model View Controler) avec la notion de QtAbstractItemModel(et tous ses dérivés) proposée par Qt...
Ce qu'il faut bien comprendre, c'est qu'il s'agit de deux choses totalement différentes!
Le "modèle" de la notion Modèle Vue Contrôleur (oui, je l'ai traduite pour la cause ) représente ce que l'on pourrait appeler tes "données métier", c'est à dire, tous les types de donnée ainsi que les règles d'utilisation qu'ils imposent que tu pourras créer afin permettre à l'ordinateur de les représenter en mémoire et de les manipuler (le plus sécuritairement possible, pourrait-on ajouter).
Ce seront des type définis par l'utilisateurs (des classes, des structures, des énumérations et plus rarement des unions) qui te permettront de gérer les différentes "notions" dont le programme a besoin pour fournir le résultat que tu attends de sa part.
La vue du modèle MVC n'est, quant à elle, qu'une "représentation particulière" des données métier qui est organisée de manière à les rendre "facilement compréhensibles" par l'utilisateur du programme (ou par n'importe quel système auquel nous pourrions envoyer "tout ou partie" des données afin qu'elles soit traitées de manière séparée).
Et, "entre les deux" (comprends: entre les données métier et la vue) nous aurons un "contrôleur" dont le but est, quand la vue (la représentation particulière des données métier) subit un changement (par exemple, que tu as remplacé la valeur 2 affichée par un widget quelconque par la valeur 3) d'informer le modèle (les données métier)du changement subit et de transmettre (au besoin) la réaction du modèle (surtout s'il a refusé la modification) à la vue.
NOTA: Le principe énoncé ici est "simplifié à l'extrême", ce qui implique que j'y ai introduit certaines erreurs "par facilité" et dont je suis bien conscient. Il présente cependant l'énorme avantage -- selon moi -- de te donner une idée "relativement correcte" de ce que représente cette approche MVC
D'un autre coté, nous avons le QtAbstractItemModel de Qt dont le but est d'apporter une "réponse de base" à une question (sommes toutes) "relativement simple" qui est
Je dispose de données métier "complexes" (dans le sens: qui sont composées de multiple informations). comment puis-je organiser et manipuler ces données pour qu'elles soient affichées de la manière qui me convient à l'endroit qui me convient?
Et la réponse est assez surprenante, mais tout à fait "logique" dans le cas présent:
On va "déconstruire" toutes ces données métier afin d'en récupérer les "données de base" (comme des entiers, des réels ou des chaines de caractères) qui "nous intéressent" (que nous voulons afficher), que nous allons placer à un "endroit logique" (pour pouvoir les récupérer plus facilement) et pour lesquelles nous pourrons définir la manière dont elles devront être affichées.
Ce qu'il est important de comprendre, c'est qu'il n'est EN AUCUN CAS question de ne pas maintenir les données métier en mémoire "sous prétexte qu'elles sont représentées dans le QAbstractItemModel":
il faut IMPERATIVEMENT avoir, d'un coté, le modèle de l'approche Modèle Vue Controleur "quelque part" en mémoire et, d'un autre coté, il faut avoir le résultat "l'analyse" de ce modèle qui en a été fait afin de l'intrduire dans le QtAbstractItemModel qui se trouve "ailleurs, quelque part" en mémoire.
De plus, il faut savoir que, si tu envisages d'avoir plusieurs représentations différentes de tes données (plusieurs vues différentes), chaque vue doit avoir "sa propre analyse" des données métier. Il n'est absolument pas question d'aller "partager" des morceaux d'analyses faite par une vue avec une autre: chaque vue fait sa propre analyse afin de l'utiliser.
Ce qui pourra ** éventuellement ** être réutilisé, ce sera la partie "controleur" du MVC, à savoir la partie à laquelle "les différentes vues" pourront s'adresser afin de signaler que
La valeur 5 associée à tel élément a été remplacée par la valeur "salut".
Le rôle du contrôleur sera alors de vérifier si "salut" lui semble être une valeur "cohérente" pour cet élément (on peut décemment penser que ce ne soit pas le cas présent).
Si oui, il informera les données métier du changement apporté.
Et, pour finir, s'il ne reçoit pas de "contre ordre" et qu'il ne se fait pas "insulter" par les données métier, il pourra alors "valider" le changement, et signaler aux différentes vues qu'il est temps de "mettre leur analyse à jour" (afin d'afficher la nouvelle valeur pour l'élément).
Voilà, "en gros", la manière dont on devrait travailler avec Qt
Pourquoi as-tu pris la peine de m'expliquer tout cela?
En voilà une bonne question, que tu ne manqueras surement pas de me poser, j'imagine
Hé bien, c'est pour te faire comprendre que ton code
n'a en fait que très peu de sens de mon point de vue:
Si m_model représente, comme je l'imagine, ton "modèle MVC" (tes données métier), il n'y a absolument aucune raison pour en faire un QSharedPointer (un pointeur partagé "made in Qt").
Et, de la même manière, comme chaque QtAbstractItemModel appartient forcément à une vue qui va être la seule à l'utiliser et qui n'utilisera que lui et lui seul, ce n'est pas la liste des AbstractViewModel que tu dois garder, mais bien ... la liste des vues (qui disposeront à chaque fois du modèle qui leur est propre)
En travaillant de cette manière, tu auras la certitude que "chacun restera chez soi" et que "les vaches seront bien gardées"
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
Tu as raison, j'ai en effet implémenté un pattern MVC pour ce soft et ma classe ModelData représente bien le model du MVC
En revanche ma classe AbstractViewModel ne prend pas le rôle de QtAbstractItemModel
Avant de rentrer dans les explications que j'essaierai de garder concise, je tiens à préciser que je sais que ce que j'ai fait est loin d'être parfait et que j'aurai surement pu utiliser les mécanismes de vue et de model proposé par Qt mais je ne maitrisais pas ces fonctionnalités, je ne voyait pas comment les appliquer facilement à mon besoin, et le patron me demandait surtout d'avoir rapidement un truc fonctionnel donc pas le temps de passer 1 mois à comprendre comment ça marche. Sachant que je suis le seul à mon taff à savoir faire plus qu'un "Hello world", ça ne dérange personne (à part mon côté perfectionniste que j'ai du mettre de côté) si le soft n'est pas fait dans les rêgles de l'art.
J'ai une arduino cachée au fond d'un boitier qui active de façon cyclique des électrodes en pilotant des relais. Les techniciens de savent pas reprogrammer l'arduino pour modifier le cycle en fonction du cachier des charges de l'essai à réaliser.
Mon logiciel a donc pour but de fournir une interface graphique permettant de reprogrammer le cycle en "no code" pour que n'importe qui puisse utiliser le boitier avec l'Arduino.
Au fur et à mesure, le boitier s'étoffe. On vient de rajouter un capteur d'humidité/température et on prévoit d'ajouter d'autres capteurs.
Mon model (qui décrit le cycle d'activation des électrodes par l'Arduino) s'étoffe aussi. Utilise-t-on le capteur ? Oui/Non. Quelle fréquence d'aquisition pour le capteur, combien de temps d'aquisition
J'ai donc mis une QTabWidget dans ma MainWindow (qui joue le rôle de contrôleur ici) et chaque onglet contient une vue différente. Une première vue affiche les éléments du modèles relatifs au cycle d'électrode
Et l'autre onglet affiche la vue relative au reglage du capteur
Ces 2 différentes vue (puis 3, puis 4 vue ect ... quand on ajoutera d'autres capteurs) affichent donc différents éléments du même model, c'est pour cela qu'un même model est partagé par toutes les vues
Ma classe AbstractViewModel me permet juste de faciliter la création de nouvelle vue en implémentant certains comportements de bases et communes à toutes les vues ainsi que quelques fonctions virtuelles pures qui doivent être spécialisées dans toutes les vues (Promis c'est pas un destructeur virtuel pur comme dans mon autre sujet, là je pense utiliser correctement les fonctions virtuelles pures )
Ce qu'il est important de comprendre, c'est qu'il n'est EN AUCUN CAS question de ne pas maintenir les données métier en mémoire "sous prétexte qu'elles sont représentées dans le QAbstractItemModel":
il faut IMPERATIVEMENT avoir, d'un coté, le modèle de l'approche Modèle Vue Controleur "quelque part" en mémoire et, d'un autre coté, il faut avoir le résultat "l'analyse" de ce modèle qui en a été fait afin de l'intrduire dans le QtAbstractItemModel qui se trouve "ailleurs, quelque part" en mémoire.
On implemente comme on veut, ce n'est pas une obligation de garder les données hors du QAbstractItemModel. Dans certains cas, ca sera la cas, par exemple QSqlTableModel, et d'autres cas non, par exemple QStandardItemModel.
koala01 a écrit:
Et, de la même manière, comme chaque QtAbstractItemModel appartient forcément à une vue qui va être la seule à l'utiliser et qui n'utilisera que lui et lui seul, ce n'est pas la liste des AbstractViewModel que tu dois garder, mais bien ... la liste des vues (qui disposeront à chaque fois du modèle qui leur est propre)
Aucun problème à avoir plusieurs vues sur le même modèle.
(Hors sujet, il s'agit d'un MVD dans Qt, pour "delegate")
ThibaultVnt a écrit:
Avant de rentrer dans les explications que j'essaierai de garder concise, je tiens à préciser que je sais que ce que j'ai fait est loin d'être parfait et que j'aurai surement pu utiliser les mécanismes de vue et de model proposé par Qt mais je ne maitrisais pas ces fonctionnalités
Je n'ai rien vu de choquant (mais je n'ai pas tout le code et j'ai juste survolé). A la rigueur, je me demande pourquoi tu n'as pas utilisé QAbstractItemView, mais c'est pas forcément un problème en soi.
Je pense que le nom est juste mal choisi. Pour ce que l'on voit dans le code, il s'agit juste d'une vue, pas un ViewModel. ModelData aussi d'ailleurs, c'est pas très explicite.
QSharedPointer - Crash à la fermeture du programme
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Si ton modèle à un QObject comme parent, alors tu as un double ownership et double delete. Il faut choisir l'un des 2.
Discord NaN. Mon site.
Discord NaN. Mon site.
Discord NaN. Mon site.