PDA

View Full Version : Master/detail presentation



cia.michele
26th December 2009, 17:42
Merry XMas to everybody,
probably I'm alone on this forum at 26th December, but, I've a problem, and I hope to solve it as soon as possibile.
This is my problem: I have a two table in a MYSQL database, that show a master/detail relation. I'd like to show these data on my form with a single widget (perhaps treeview), but, I'm a newbie of QT and I don't know from where I must start.
I'd like to obtain a widget like a tree view, in which every master elemet is a treeview element and, opening this, it show a table (with different headers too) where are shown the details element. Something Like the MS Hierarchical FlexGrid of Microsoft Visual Studio 6.

Have you got any ideas?

Thanks for your help

Michele

psih128
26th December 2009, 19:39
As far as I know there is no easy way to do this with a single liner - you will probably have to develop your own model to present the data from your database in a hierarchical manner.

There are two classes that could have helped if a table-like solution was OK: QSqlTableModel and QSqlQueryModel. These two will let you to present the data from your DB in a QTableView widget with little effort. You also mentioned that the headers in the tables are different. If they are completely different, then you could actually present the data with QSqlQueryModel in a single table.

Otherwise you will probably have to take a look at model development and work out your own model. You could inherit from QSqlQueryModel and extend the model to make it hierarchical. If you are completely new to model development, then it will probably take some time...

cia.michele
27th December 2009, 20:53
As I said, I'm a newbie of c++ & QT programming, so, it I don't know if this is the correct way but, I could use an hierarchical model (perhaps a QSqlRelationalModel or a QTreeModel) and build a different view or delegates. I thought this: for my uses it's enought that the child items of a single parent could be added to a QTableWidget; in this way, I take the items from the hierarchical model but I add to an inner table (all the child items of each parent are shown bye an inner table below the parent intem, with the same indentation of the child). However, I don't know how obtain this, i should create an inner table for each parent item of QTreeWidget for example; fill this control is possible in some way. I should build a custom delegate? or I should build only a custom Widget?
Have you got any ideas or example that I can follow?

Thanks a lot for your time.

Michele
PS. I've see also the QSqlRelationalModel example of qt (relative to artist/album/tracks managing, but in that case it uses 3 different controls on the form)

numbat
30th December 2009, 15:54
This is my little attempt to create a master/detail control with Qt by embedding a QTreeView inside a QTreeView. Inspired by the frozen column example (http://doc.trolltech.com/latest/itemviews-frozencolumn.html). Enjoy.
mainwindow.h


#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui/QMainWindow>

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = 0);
~MainWindow();
};


#include <QAbstractItemDelegate>
#include <QFontMetrics>
#include <QModelIndex>
#include <QSize>
#include <QTreeView>
#include <QStyledItemDelegate>
#include <QResizeEvent>

class QAbstractItemModel;
class QObject;
class QPainter;


class HGrid : public QTreeView
{
Q_OBJECT
public:
HGrid(QWidget * parent = 0);
public slots:
void expanding(const QModelIndex & index);
void collapsing(const QModelIndex & index);
private:
QTreeView * tv;
void collapseOthers(QAbstractItemModel * m, QModelIndex sp);
};


class TableDelegate : public QStyledItemDelegate
{
Q_OBJECT

public:
TableDelegate(QObject *parent, QTreeView* child);

void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;

QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index ) const;

private:
QTreeView * tv;
};

#endif // MAINWINDOW_H

and mainwindow.cpp


#include "mainwindow.h"
#include <QTreeView>
#include <QStandardItemModel>
#include <QString>
#include <QPainter>
#include <QDebug>
#include <QScrollBar>
#include <QtGui/QApplication>

const int margin = 12;
const int sMargin = 3;
const int ChildModelRole = Qt::UserRole + 1;
Q_DECLARE_METATYPE( QAbstractItemModel * );

HGrid::HGrid(QWidget * parent) :
QTreeView(parent), tv(0)
{
/* Register QAbstractItemModel* so we can store it in a QVariant. */
qRegisterMetaType<QAbstractItemModel*>("QAbstractItemModel*");
tv = new QTreeView(this->viewport());
tv->hide();
TableDelegate * td = new TableDelegate(this, tv);
connect(this, SIGNAL(expanded(QModelIndex)), SLOT(expanding(const QModelIndex)));
connect(this, SIGNAL(collapsed(QModelIndex)), SLOT(collapsing(const QModelIndex)));
setItemDelegate(td);
setIndentation(12);
setHorizontalScrollMode(ScrollPerPixel);
setVerticalScrollMode(ScrollPerPixel);
}

void HGrid::collapseOthers(QAbstractItemModel * m, QModelIndex sp)
{
for (int i = 0; i < m->rowCount(); i++)
if (isExpanded(m->index(i, 0)))
if (m->index(i, 0) != sp)
setExpanded(m->index(i, 0), false);
}

void HGrid::expanding(const QModelIndex &index)
{
/* Hide all the other expanded sections. This way we only need one child tree-view. */
collapseOthers(model(), index);

if (index.model()->data(index.child(0,0), ChildModelRole).isValid())
{
/* Retrieve our child model. */
tv->setModel(qVariantValue<QAbstractItemModel*>(index.model()->data(index.child(0,0), ChildModelRole)));
setFirstColumnSpanned(0, index, true);
viewport()->stackUnder(tv);
tv->show();
}
}

void HGrid::collapsing(const QModelIndex &index)
{
tv->hide();
}

TableDelegate::TableDelegate(QObject *parent, QTreeView *child)
: QStyledItemDelegate(parent), tv(child)
{ }

void TableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.model()->data(index, ChildModelRole).isValid())
{
QRect rc = option.rect;
painter->save();
painter->setBrush(Qt::gray);
painter->setPen(Qt::NoPen);
painter->drawRoundedRect(rc.left() + sMargin, rc.top() + sMargin,
rc.width() - (sMargin * 2), rc.height() - (sMargin * 2),
3.5, 3.5);
painter->restore();
tv->resize(rc.width() - (margin * 2), rc.height() - (margin * 2));
tv->move(rc.left() + margin, rc.top() + margin);
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}

QSize TableDelegate::sizeHint(const QStyleOptionViewItem & option,
const QModelIndex & index) const
{
if (index.model()->data(index, ChildModelRole).isValid())
return QSize(option.rect.width(), 200);
else
return QStyledItemDelegate::sizeHint(option, index);
}

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
HGrid * hgrid = new HGrid(this);


/* Our master can have any number of columns. */
QStandardItemModel * m = new QStandardItemModel(0, 1);
QStandardItem *parentItem = m->invisibleRootItem();

for (int i = 0; i < 10; ++i) {
QStandardItem *item = new QStandardItem(QString("Parent %0").arg(i));
parentItem->appendRow(item);

QStandardItem * ph = new QStandardItem("Placeholder");
item->appendRow(ph);
ph->setFlags(Qt::NoItemFlags);

/* Now make a child model to fill our child tree view. */
QStandardItemModel * m2 = new QStandardItemModel;
for (int j = 0; j < 10; j++)
{
QList<QStandardItem*> items;
for (int column = 0; column < 6; ++column)
{
items.push_back(new QStandardItem(QString("Item %0").arg(column)));
}
m2->invisibleRootItem()->appendRow(items);
}

/* And store a pointer to our child model in the master model. */
m->setData(ph->index(), QVariant(QMetaType::type("QAbstractItemModel*"), &m2), ChildModelRole);
}

hgrid->setModel(m);
setCentralWidget(hgrid);
}

MainWindow::~MainWindow()
{ }


int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

graciano
30th December 2009, 16:09
Hi,

I'm looking at the "Implementing Master-Detail Forms" in the "C++ GUI Programming with C++".

Does anyone know other examples like this available in the Internet?

I tried some googling but i ended up in the example above.

Thanks

cia.michele
27th January 2010, 08:27
Happy new Year to everybody!
Thanks a lot Numbat for your solution! It's exactly what I want obtain. :) But I've yet a question: I see in this example it is used some for loop and strings to fill the columns... how I can link the view to a gerarchical model? (I think to QSQLRelationalModel)
By this, I'll have an indipendent widget that I can simply put on my form ad use it simply passing on it the model of my data.

Thanks for your help!

I'll show you, if you want how I'll use this.

Michele

cia.michele
17th August 2010, 10:54
Goodmorning to all.
I'm working with the example of numbat (thanks a lot for this!) it's exaclty what I need!! But... working on it I've see this example use string constants to populate two level grid and all the sistem of "frozen column" is based over a single model.

I've this problem now: I'd like to use this view with an SQL model. The most simple way is read all row and column of two QSqlTableModel (one for the first level and one for the second level) and set the strings into the treeview (using the tecnic showed by numbat), but i'd like to make something better.

I'd like to create an SQL model then inglobe the two different Role and in which every item has different parent if need. I'd like to create ad QSqlTreeModel or something like this.

I try to do this but my experience on QT is non enought and I loose myself.

Does anybody any ideas of how can I create a model the can populate the grid? I thought to pass two tables and two fields to something like "select()" method of QSqlTableModel and use it as base... but I don't know which this cause.

Have you got any ideas and some time to try to create it togheter?

Thanks a lot for your time!

Michele

cia.michele
18th August 2010, 08:55
I've resolve the question adding a method that implements the code of the numbat's example:


void HGrid::setMasterSlaveModel(QSqlTableModel *master, QSqlTableModel *slave, QString fieldmaster, QString fieldslave)
{
// Our master can have any number of columns.
QStandardItemModel * m = new QStandardItemModel();
QStandardItem *parentItem = m->invisibleRootItem();

//Scorro il model principale per riga
for (int i=0; i< master->rowCount();i++){

//Leggo il record corrente del model
QSqlRecord rec = master->record(i);
//Creo una lista di colonne
QList<QStandardItem*> colsFatt;

//Scorro tutte le colonne
for (int j=0; j<master->columnCount();++j){

//Aggiungo i dati delle colonne alla lista
colsFatt.push_back(new QStandardItem(rec.value(j).toString()));
}

//Aggiungo la lista dei dati delle colonne al model
parentItem->appendRow(colsFatt);

//Creo un elemento fittizio per aggiungere i dati di secondo livello
QStandardItem * ph = new QStandardItem( "Placeholder");
colsFatt.at(0)->appendRow(ph);
ph->setFlags(Qt::NoItemFlags);

QStandardItemModel * m2 = new QStandardItemModel;
slave->setFilter(QString("%1=%2").arg(fieldslave).arg(rec.value(fieldmaster).toInt ()));
for (int k = 0; k < slave->rowCount(); k++)
{
QList<QStandardItem*> colsDett;
QSqlRecord recDett = slave->record(k);
for (int x = 0; x < slave->columnCount(); ++x)
{
colsDett.push_back(new QStandardItem(recDett.value(x).toString()));
}
m2->invisibleRootItem()->appendRow(colsDett);
}
m->setData(ph->index(), QVariant(QMetaType::type("QAbstractItemModel*"), &m2), ChildModelRole);
}

this->setModel(m);
}


But I don't like it so much. Is there any smarter solution? Have you got any ideas?

Thanks a lot for your time.

Michele

cia.michele
18th August 2010, 13:42
And, with this structure, how can I show images on the first column (of both the QTreeView). One image for the items of firts level (first QTreeview) and an other for the items of internal QTreeview (second level).

Have you got any idea?

Thank a lot for your time

Michele