Bienvenue sur le forum !

Si vous souhaitez rejoindre la communauté, cliquez sur l'un de ces boutons !

Qt 5 : 5.7.1 - Qt Creator : 4.2.0 - Qt Installer : 2.0.3 - JOM : 1.1.2 - Qt Build suite : 1.7.0 - VS Qt 5 : 2.0.0

Arbre Genealogique "ouvert"

Bonsoir à tous,
Grace à l'aide de PapaJaac et de AKS, je veux vous faire partager un code qui permet de créer un arbre, type arbre généalogique, à partir d'une base de données.

Je ne suis pas professionnel de la programmation donc il y a sûrement de nombreux points à perfectionner, mais je me devais de rendre à César(s) ce qui lui(leur) appartient afin de les remercier pour leur longues heures de disponibilité. ^:)^

Nota:
-Je fais de la généalogie animale, il y a donc une classe Animal qu'il vous faudra modifier pour exploiter un autre type de données.
-J'ai dérivé la classe QLabel (sous le conseil de PapaJaac) afin de réimplémenter le mousePressEvent. Il permet, dans l'exemple d'afficher un QToolTip par click-droit - Rien en click-gauche mais, du coup, vous êtes libres. ;)
-J'ai ajouté une classe dérivée de QString afin de découper un QString pour en effacer des parenthèses. Ce n'est pas vraiment un QRegExp mais c'est simple et très pratique.
-Enfin, j'utilise des BD SQLite dans cet exemple, donc il vous faudra revoir l'écriture de l'appel pour une autre DB et, bien sûr, modifier son nom.

Le résultat en image en fichier joint. ;)

Pour le code complet :
Arbre.pro:

#-------------------------------------------------
#
# Project created by QtCreator 2015-07-27T22:03:04
#
#-------------------------------------------------

QT += core gui sql

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = Arbre
TEMPLATE = app

CONFIG +=c++11

SOURCES += main.cpp\
myContainerWidget.cpp \
parentheseString.cpp

HEADERS += \
myContainerWidget.h \
ConnectDB.h \
parentheseString.h
ConnectDB.h:
#ifndef CONNECTDB_H
#define CONNECTDB_H

#include <QApplication>
#include <QMessageBox>
#include <QtSql>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>

#include <QDebug>

QSqlDatabase DB;

static bool ConnectDB()
{
DB = QSqlDatabase::addDatabase("QSQLITE");
DB.setDatabaseName(QApplication::applicationDirPath() + "/DBTest.db3");
if (!DB.open()) {
QMessageBox::critical(0, qApp->tr("Cannot open database"),
qApp->tr("Unable to establish a database connection.\n"
"This example needs SQLite support.\n\n <i> Click Cancel to exit.</i>"), QMessageBox::Cancel);
return false;
}
else
{
QSqlQuery Query;
Query.prepare("PRAGMA foreign_keys = ON;");
qDebug() << "Starting DB last Error:" << Query.lastError().text();

return true;
}
}

static bool DisconnectDB()
{
if (DB.isOpen())
{
DB.commit();DB.close();qDebug()<<"Closing DB";
DB.removeDatabase(QSqlDatabase::defaultConnection);
return true;
}
else return false;
}

#endif // CONNECTDB_H
ensuite les classes:
-myContainerWiget:
.h

#ifndef MYCONTAINERWIDGET_H
#define MYCONTAINERWIDGET_H

#include <QWidget>
#include <QScrollArea>
#include <QLabel>
#include <QDate>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QHash>
#include <QToolTip>

#include <QPainter>
#include <QPaintEvent>
#include <QGraphicsDropShadowEffect>

#include <QDebug>

//contenu affichage : numero + livre(en1 ou en 4)\n , (CH), prenom, affixe, \n sexe, date naissance, \n couleur
class Animal
{
protected:
int animalID=-1, PereID=-1, MereID=-1;
QString isCH={"<font-color #FF0000>CH<\font>"}, prenom, affixe, numeroLivre, sexe, couleur;
QDate birthDate;

public:
Animal();

int getID() const {return animalID;}
int getPereID() const {return PereID;}
int getMereID() const {return MereID;}
QString getChampion() const {return isCH;}

QString getName() const {return prenom;}
QString getAffix() const {return affixe;}
QString getNLivre() const {return numeroLivre;}
QString getSex() const {return sexe;}
QString getColour() const {return couleur;}
QDate getBDate() const {return birthDate;}

void setID(const int id){animalID=id;}
void setName(const QString& n){prenom = n;}
void setAffix(const QString& a){affixe = a;}
void setNLivre(const QString& l){numeroLivre = l;}
void setSex(const QString& s){sexe = s;}
void setColour(const QString& c){couleur = c;}
void setBDate(const QDate& d){birthDate = d;}
void setPereID(const int id){PereID=id;}
void setMereID(const int id){MereID=id;}

};

struct ArbreGenealogique
{
QWidget *containerWidget;
QLabel *currentLabel;
Animal animalChild; // pour repère en DB
ArbreGenealogique *pere, *mere;
};

class myLabel : public QLabel
{
public:
myLabel (QWidget * parent=0);

void mousePressEvent(QMouseEvent *e);
};

class myContainerWidget : public QWidget
{
Q_OBJECT
public:
explicit myContainerWidget(QWidget *parent = 0);
QHash<Animal, int> animalListID;

signals:

public slots:

protected:
void paintEvent(QPaintEvent *event);
void tracerLignesDansArbre(ArbreGenealogique *node);

ArbreGenealogique *construitArbre(int nbGen, int idAnimal);

ArbreGenealogique *arbre;
QPainter painter;

};

//Reimplementation des methodes necessaires pour le QHash
inline bool operator==(const Animal &animal1, const Animal &animal2)
{
return ((animal1.getID()) == (animal2.getID()));
}
inline uint qHash(const Animal &key)
{
return qHash(key.getID());
}

#endif // MYCONTAINERWIDGET_H
</pre>
jpg
jpg
ResultatArbre.jpg
46K
zip
zip
DBTest.zip
770B

Réponses

  • .cpp

    #include "myContainerWidget.h"
    #include "parentheseString.h"

    #include "ConnectDB.h"
    #include

    Animal::Animal()
    {
    }

    myLabel::myLabel(QWidget* parent) : QLabel (parent)
    {
    setFrameShadow(QFrame::Plain);
    setFont(QFont("Georgia",10));
    setStyleSheet("border:2px solid grey; "
    "border-radius: 8px; "
    "background-color: white;");
    setMinimumWidth(100);
    setFixedHeight(70);
    setAlignment(Qt::AlignTop | Qt::AlignLeft);
    setBackgroundRole(QPalette::Light);
    setAutoFillBackground(true);

    //Ajout d'une ombre portee
    QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect;
    effect->setBlurRadius(8);
    effect->setXOffset(5);
    effect->setYOffset(5);
    effect->setColor(Qt::lightGray);

    setGraphicsEffect(effect);
    }

    //IMPLEMENTATION DU CLIC SUR LES LABELS
    void myLabel::mousePressEvent(QMouseEvent *e)
    {
    if (e->button()== Qt::RightButton)
    {
    if (this->objectName()!="0")
    {
    qDebug()<<"Right single-click";

    //Remplissage infobulle avec conversion de l'ecriture de l'affixe, suppression des parenthèses.
    QString infoText= "<h2>" + myContainerWidget().animalListID.key(this->objectName().toInt()).getName() + " " + parentheseString().parentheseRemove(myContainerWidget().animalListID.key(this->objectName().toInt()).getAffix()) + "\n"
    +"un texte qui ne sert à rien pour le moment";
    QToolTip::showText(e->globalPos(),infoText);
    }
    }
    else
    if (e->button() == Qt::LeftButton)
    {
    qDebug()<<"Left single-click";
    }
    }

    //POINT D'ENTREE
    myContainerWidget::myContainerWidget(QWidget *parent) : QWidget(parent)
    {
    if (ConnectDB())
    {
    arbre = construitArbre(3, 1);//A RENDRE AUTOMATIQUE PAR APPEL EXTERNE

    QHBoxLayout *layout = new QHBoxLayout;
    layout->setMargin(0);

    layout->addWidget(arbre->containerWidget);
    setLayout(layout);
    }
    DisconnectDB();
    }

  • ArbreGenealogique* myContainerWidget::construitArbre(int nombreGenerations, int idAnimal)
    {
    //INITIALISE UNE REQUETE SUR LA BASE AFIN DE REMPLIR LES DONNEES DE L ANIMAL
    QSqlQuery Qry;
    Qry.prepare(QString("SELECT * FROM Animal WHERE id = %1").arg(idAnimal));

    if(nombreGenerations == 1)
    {
    ArbreGenealogique *arbreUnElement = new ArbreGenealogique;
    arbreUnElement->animalChild.setID(idAnimal);

    //Appel DB
    if (Qry.exec())
    {
    Qry.next();
    arbreUnElement->animalChild.setName(Qry.value(1).toString());
    arbreUnElement->animalChild.setAffix(Qry.value(2).toString());
    if (Qry.value(3).toInt()==2)
    arbreUnElement->animalChild.setSex("F");
    else
    {
    if (Qry.value(3).toInt()==1)
    arbreUnElement->animalChild.setSex("M");
    }
    //INUTILE DE RENSEIGNER DES PARENTS ICI
    }
    else
    qDebug() << " !! Select statement error: " <<Qry.lastError().text();

    Qry.finish();
    Qry.clear();

    animalListID.insert(arbreUnElement->animalChild,idAnimal);

    arbreUnElement->currentLabel = new myLabel(); // Enlever les /* */ en-dessous pour exploiter la suppression de parentheses.
    arbreUnElement->currentLabel->setText(QString::number(arbreUnElement->animalChild.getID()) + "-" + arbreUnElement->animalChild.getName()+ " " + /*parentheseString().parentheseRemove*/(arbreUnElement->animalChild.getAffix())+ "\n"
    + arbreUnElement->animalChild.getSex());
    //Stocke dans le nom l'id animal pour le click droit.
    arbreUnElement->currentLabel->setObjectName(QString::number(arbreUnElement->animalChild.getID()));
    qDebug()<< "Atribut nom: "<< arbreUnElement->currentLabel->objectName();


    arbreUnElement->containerWidget = new QWidget;
    QHBoxLayout *layout = new QHBoxLayout();
    layout->setMargin(5);
    layout->setSpacing(0);

    layout->addSpacing(5);
    layout->addWidget(arbreUnElement->currentLabel);
    layout->addSpacing(5);

    arbreUnElement->containerWidget->setLayout(layout);

    arbreUnElement->pere = nullptr;
    arbreUnElement->mere = nullptr;

    return arbreUnElement;
    }
    else
    {
    ArbreGenealogique *petiteFamille = new ArbreGenealogique;
    petiteFamille->animalChild.setID(idAnimal);

    //Appel DB
    if (Qry.exec())
    {
    Qry.next();
    petiteFamille->animalChild.setName(Qry.value(1).toString());
    petiteFamille->animalChild.setAffix(Qry.value(2).toString());
    if (Qry.value(3).toInt()==2)
    petiteFamille->animalChild.setSex("F");
    else
    {
    if (Qry.value(3).toInt()==1)
    petiteFamille->animalChild.setSex("M");
    }
    petiteFamille->animalChild.setPereID(Qry.value(4).toInt());
    petiteFamille->animalChild.setMereID(Qry.value(5).toInt());
    }
    else
    qDebug() << " !! Select statement error: " <<Qry.lastError().text();

    Qry.finish();
    Qry.clear();

    animalListID.insert(petiteFamille->animalChild,idAnimal);

    petiteFamille->currentLabel = new myLabel();
    //Remplissage Texte -- enlever le /* */pour supprimer la presence de parentheses
    petiteFamille->currentLabel->setText(QString::number(petiteFamille->animalChild.getID()) + "-" +petiteFamille->animalChild.getName() + " " + /*parentheseString().parentheseRemove*/(petiteFamille->animalChild.getAffix())+ "\n"
    + petiteFamille->animalChild.getSex());
    //Stocke dans le nom l'id animal pour le click droit.
    petiteFamille->currentLabel->setObjectName(QString::number(petiteFamille->animalChild.getID()));
    qDebug()<< "Atribut nom: "<< petiteFamille->currentLabel->objectName();

    //une fois encore, les reglages de ce frame enfant
    petiteFamille->pere = construitArbre(nombreGenerations - 1, petiteFamille->animalChild.getPereID());
    petiteFamille->mere = construitArbre(nombreGenerations - 1, petiteFamille->animalChild.getMereID());

    //Construction de l'arbre

    petiteFamille->containerWidget = new QWidget();

    QVBoxLayout *containerLayout = new QVBoxLayout;
    containerLayout->setSpacing(0);
    containerLayout->setMargin(0);

    QHBoxLayout *layoutForCurrentChild = new QHBoxLayout();
    layoutForCurrentChild->setMargin(0);

    layoutForCurrentChild->addStretch();
    layoutForCurrentChild->addWidget(petiteFamille->currentLabel);
    layoutForCurrentChild->addStretch();

    QWidget *currentChildContainer = new QWidget();
    currentChildContainer->setLayout(layoutForCurrentChild);
    currentChildContainer->setMinimumHeight(petiteFamille->currentLabel->height()+10);//pour la gestion des ombres et du trait

    QHBoxLayout *layoutForParents = new QHBoxLayout();
    layoutForParents->setMargin(0);

    layoutForParents->addStretch();
    layoutForParents->addWidget(petiteFamille->pere->containerWidget);
    layoutForParents->addStretch();
    layoutForParents->addWidget(petiteFamille->mere->containerWidget);
    layoutForParents->addStretch();

    QWidget *parentsWidgetContainer = new QWidget();
    parentsWidgetContainer->setLayout(layoutForParents);

    QWidget *widgetBetweenChildAndParents = new QWidget();
    widgetBetweenChildAndParents->setMinimumHeight(petiteFamille->currentLabel->height()+10);

    QSizePolicy policyBetweenWidget;
    policyBetweenWidget.setVerticalStretch(1);
    policyBetweenWidget.setHorizontalPolicy(QSizePolicy::Expanding);
    policyBetweenWidget.setVerticalPolicy(QSizePolicy::Expanding);
    widgetBetweenChildAndParents->setSizePolicy(policyBetweenWidget);

    QSizePolicy policyForParentsWidget;
    policyForParentsWidget.setVerticalStretch(nombreGenerations-1);
    policyForParentsWidget.setHorizontalPolicy(QSizePolicy::Expanding);
    policyForParentsWidget.setVerticalPolicy(QSizePolicy::Expanding);
    parentsWidgetContainer->setSizePolicy(policyForParentsWidget);

    containerLayout->addWidget(parentsWidgetContainer);
    containerLayout->addWidget(widgetBetweenChildAndParents);
    containerLayout->addWidget(currentChildContainer);

    petiteFamille->containerWidget->setLayout(containerLayout);

    return petiteFamille;
    }
    }

    //DECLENCHE LE 'PAINT' POUR TRACER LES LIGNES DE LIAISON
    void myContainerWidget::paintEvent(QPaintEvent *event)
    {
    painter.begin(this);
    tracerLignesDansArbre(arbre);
    painter.end();

    event->accept();
    }

  • //TRACE LES LIGNES
    void myContainerWidget::tracerLignesDansArbre(ArbreGenealogique *node)
    {
    if(node->mere == nullptr || node->pere == nullptr)
    return;

    QRect rectFils = node->currentLabel->geometry();
    QRect rectPere = node->pere->currentLabel->geometry();
    QRect rectMere = node->mere->currentLabel->geometry();

    rectFils.moveTopLeft(node->currentLabel->parentWidget()->mapTo(this, rectFils.topLeft()));
    rectPere.moveTopLeft(node->pere->currentLabel->parentWidget()->mapTo(this, rectPere.topLeft()));
    rectMere.moveTopLeft(node->mere->currentLabel->parentWidget()->mapTo(this, rectMere.topLeft()));

    //Les lignes desactivees crees un chemin a base de droites.

    //QLine line1 = QLine(0.5 * (rectFils.topLeft() + rectFils.topRight()), 0.5 * (rectPere.bottomLeft() + rectPere.bottomRight()));
    //QLine line2 = QLine(0.5 * (rectFils.topLeft() + rectFils.topRight()), 0.5 * (rectMere.bottomLeft() + rectMere.bottomRight()));

    // QLine1 vers QPainterPath
    QPainterPath pathLineLeft;//trace de enfant vers partie gauche avec un virage a mi-chemin des 2 frames
    pathLineLeft.moveTo(0.5 * (rectFils.topLeft() + rectFils.topRight()));//Depart en "milieu" de boite
    pathLineLeft.lineTo(0.5 * (rectFils.topLeft().x() + rectFils.topRight().x()), .5*(rectFils.topRight().y() + rectPere.bottomRight().y()));//elevation verticale seule
    //pathLineLeft.lineTo(0.5 * (rectPere.bottomLeft().x() + rectPere.bottomRight().x()),.5*(rectFils.topRight().y() + rectPere.bottomRight().y()));//trait jusqu au milieu
    //pathLineLeft.lineTo(.5*(rectPere.bottomLeft().x()+rectPere.bottomRight().x()),.5*(rectPere.bottomLeft().y()+rectPere.bottomRight().y())) ;
    pathLineLeft.lineTo(rectPere.bottomRight().x(),.5*(rectFils.topLeft().y() + rectPere.bottomLeft().y()));
    pathLineLeft.quadTo(0.5 * (rectPere.bottomLeft().x() + rectPere.bottomRight().x()),.5*(rectFils.topLeft().y() + rectPere.bottomLeft().y()),.5*(rectPere.bottomLeft().x()+rectPere.bottomRight().x()),.5*(rectPere.bottomLeft().y()+rectPere.bottomRight().y()));


    // QLine2 vers QPainterPath
    QPainterPath pathLineRight;//trace de enfant vers partie droite avec un virage a mi-chemin des 2 frames
    pathLineRight.moveTo(0.5 * (rectFils.topLeft() + rectFils.topRight()));//Depart en "milieu" de boite
    pathLineRight.lineTo(0.5 * (rectFils.topLeft().x() + rectFils.topRight().x()), .5*(rectFils.topRight().y() + rectMere.bottomLeft().y()));//elevation verticale seule
    //pathLineRight.lineTo(0.5 * (rectMere.bottomLeft().x() + rectMere.bottomRight().x()),.5*(rectFils.topRight().y() + rectMere.bottomLeft().y()));//trait jusqu au milieu
    //pathLineRight.lineTo(.5*(rectMere.bottomLeft().x()+rectMere.bottomRight().x()),.5*(rectMere.bottomLeft().y()+rectMere.bottomRight().y())) ;
    pathLineRight.lineTo(rectMere.bottomLeft().x(),.5*(rectFils.topRight().y() + rectMere.bottomLeft().y()));
    //pathLineRight.quadTo(QPoint(rectMere.bottomLeft().x(),.5*(rectFils.topRight().y() + rectMere.bottomLeft().y())) , QPoint(.5*(rectMere.bottomLeft().x()+rectMere.bottomRight().x()),.5*(rectMere.bottomLeft().y()+rectMere.bottomRight().y())));
    pathLineRight.quadTo(0.5 * (rectMere.bottomLeft().x() + rectMere.bottomRight().x()),.5*(rectFils.topRight().y() + rectMere.bottomLeft().y()),.5*(rectMere.bottomLeft().x()+rectMere.bottomRight().x()),.5*(rectMere.bottomLeft().y()+rectMere.bottomRight().y()));


    QPen pen; pen.setCapStyle(Qt::RoundCap); pen.setCosmetic(true);pen.setWidth(2); pen.setColor(Qt::blue);
    painter.setPen(pen);
    painter.drawPath(pathLineLeft);
    pen.setColor(Qt::red);
    painter.setPen(pen);
    painter.drawPath(pathLineRight);

    tracerLignesDansArbre(node->mere);
    tracerLignesDansArbre(node->pere);
    }
    -parentheseString:
    .h

    #ifndef PARENTHESESTRING_H
    #define PARENTHESESTRING_H

    #include <QString>
    #include <QStringList>

    class parentheseString : public QString
    {
    public:
    parentheseString();
    QString parentheseRemove(const QString& s);
    };

    #endif // PARENTHESESTRING_H
    .cpp

    #include "parentheseString.h"

    parentheseString::parentheseString()
    {
    }

    QString parentheseString::parentheseRemove(const QString &s)
    {
    QStringList list = s.split("(",QString::KeepEmptyParts,Qt::CaseInsensitive);
    if (list.size() > 1)
    return QString(list.last().left(list.last().length()-1) + " " + list.first());
    else
    return s;
    }

    Et le main pour finir:

    #include "myContainerWidget.h"
    #include <QApplication>

    int main(int argc, char *argv[])
    {
    QApplication a(argc, argv);
    a.setStyle("fusion");

    QScrollArea scrollArea;
    scrollArea.setWidgetResizable(true);

    myContainerWidget *widgetArbre = new myContainerWidget();

    scrollArea.setWidget(widgetArbre);
    scrollArea.setMinimumSize(scrollArea.sizeHint());
    scrollArea.show();

    return a.exec();
    }
    Je vous joins la DB test que j'ai utilisé pour élaborer les essais, dans le fichier zip. Vous devez la placer dans le répertoire debug de votre compilation pour que cela fonctionne. ;)

    Merci encore à tous pour votre aide!
    Mrico
  • Merci beaucoup !
  • Je vous remets tout dans une archive car ce sera plus pratique pour vous tous. ;)
    zip
    zip
    Arbre.zip
    9K
  • Merci à toi surtout :)>-
  • August 2015 modifié
    Merci, ton source vas me servir d'inspiration pour un tout au truc ;)
  • Il y a longtemps, j'ai codé un visualiseur d'arbre avl (c'est des arbres équilibrés).

    http://renaudguezennec.eu/programmation,1-7.html
  • Bonjour,
    c’est vraiment cool de pouvoir créer un arbre généalogique qui n’est pas faite avec un papier et un crayon. Le problème c’est qu’on va devoir retenir ces tas de codes pour pouvoir en faire. Ma tête va exploser avec ça. Vous n’avez pas de solution plus simple et facile à retenir pour cela ?
  • Vous n’avez pas de solution plus simple et facile à retenir pour cela ?
    Bonjour emanuella,
    Tout dépend ce que tu entends par là... La base de l'arbre généalogique c'est l'algorithme de "l'arbre binaire". De cela tu peux déjà créer la structure qui te convient le mieux.
    Pour ce qui est de la mise en forme, chacun est libre et si tu travailles sur la même base que Qt, c'est-à-dire Modèle d'un côté et Vue de l'autre, tes objets peuvent être très malléables. Même mieux, tu peux imaginer différentes représentations visuelles de tes données ainsi. ;-)
    Une fonction clés en main est difficile car, pour ma part, je fais des arbres pour des animaux, donc la partie Modèle est obligatoirement quelque chose de plus spécifique pour chacun...
Connectez-vous ou Inscrivez-vous pour répondre.