PDA

View Full Version : Advanced QTableView



jpujolf
18th December 2006, 16:36
Hi all,

I'm trying to create an "extended" QTableView that has some useful features for me : a footer row, like the header, but in the bottom ( for column summary, counter, .. ) and a "banded header" ( in top of column headers, grouping columns in logical blocks ).

I'm trying this ExtendedView header & footer to be similar in aspect to band header & footer of ExpressQuantumGrid from www.devexpress.com ( http://www.devexpress.com/Products/VCL/ExQuantumGrid/ )

I thought 2 possible solutions :

- Copy & Paste QTableView/QHeaderView QT Code & modify to achieve this behaviour ( I've been looking QT code and it seems is like putting "my parts" in a wasp's nest... :crying: ) So I discarded ( by now ) this one.

- The easiest one for me : Create a new QExtendedTableView, subclassing QWidget and putting inside a QVBoxLayout containing a fixed height QHeaderView ( top header ), the central QTableView and a QHeaderView ( Footer ). Models are not a problem, I've solved this part yet ( the 3 widgets show correctly their own data )

But band header & footer ignore my atempts to move them as if they were part of the grid. I've tried some "hacks" but I've the following problems :

- Header/Footer does'nt follow my model SizeHints ( all columns in header & footer are allways shown with their default width ). Anyway, the models take column info from the same source of the grid, so MUST have the same sizes.

- How do i make scroll those header/footer at the same time the TableView scrolls ?

- How can I handle the propagation of grid's column resizing to band header & footer. And of course, band resizing must affect table header & footer...

Anybody can give me orientation ?

wysota
18th December 2006, 18:31
- Copy & Paste QTableView/QHeaderView QT Code & modify to achieve this behaviour ( I've been looking QT code and it seems is like putting "my parts" in a wasp's nest... :crying: )
It's practically impossible to do that because of the use of PIMPLs by Qt widgets.


- The easiest one for me : Create a new QExtendedTableView, subclassing QWidget and putting inside a QVBoxLayout containing a fixed height QHeaderView ( top header ), the central QTableView and a QHeaderView ( Footer ). Models are not a problem, I've solved this part yet ( the 3 widgets show correctly their own data )
Not the best idea, you'll be practically reimplementing the whole view interface.

Why not try another approach - simply insert a second header view in the layout of the view just under the viewport?


But band header & footer ignore my atempts to move them as if they were part of the grid. I've tried some "hacks" but I've the following problems :
You can always hide them...



- Header/Footer does'nt follow my model SizeHints ( all columns in header & footer are allways shown with their default width ). Anyway, the models take column info from the same source of the grid, so MUST have the same sizes.
I'm not sure (you can verify that by looking into Qt sources), but I think you should calculate section sizes yourself according to the width of columns.


- How do i make scroll those header/footer at the same time the TableView scrolls ?
Signals & slots.


- How can I handle the propagation of grid's column resizing to band header & footer. And of course, band resizing must affect table header & footer...
Signals & slots again...

jpujolf
18th December 2006, 19:48
It's practically impossible to do that because of the use of PIMPLs by Qt widgets.


PIMPLs ?? What do you mean. I've looked inside and it's difficult, so it's my last option... ( but you can never say is impossible... )


Not the best idea, you'll be practically reimplementing the whole view interface.


I disagree. I don't need ALL the interface. I f you do something like this, you have all the interface, but are allways maintaining it as a single Widget :



MTableView::MTableView ( QWidget * parent ) : QWidget ( parent )
{
// 3 parts : TOP, CENTRAL & FOOTER
QVBoxLayout * vboxLayout = new QVBoxLayout ( this );
vboxLayout->setSpacing(0);
vboxLayout->setMargin(0);
vboxLayout->setObjectName(QString::fromUtf8("vboxLayout"));

// TOP : Band headers
QHeaderView * tTopBand = new QHeaderView ( Qt::Horizontal, this );
tTopBand->setObjectName ( QString::fromUtf8 ( MTable_TOPBAND ) );
tTopBand->setMinimumHeight ( 20 );
tTopBand->setMaximumHeight ( 20 );
QSizePolicy spTop ( QSizePolicy::Expanding, QSizePolicy::Fixed );
spTop.setHorizontalStretch(0);
spTop.setVerticalStretch(0);
tTopBand->setSizePolicy ( spTop );
vboxLayout->addWidget ( tTopBand );

// CENTRAL : Data & column headers
QTableView * tMainGrid = new QTableView ( this );
tMainGrid->setObjectName ( QString::fromUtf8 ( MTable_CENTRAL ) );
QSizePolicy spCentral ( QSizePolicy::Expanding, QSizePolicy::Expanding );
spCentral.setHorizontalStretch(0);
spCentral.setVerticalStretch(0);
tMainGrid->setSizePolicy ( spCentral );
vboxLayout->addWidget ( tMainGrid );

tMainGrid->setSelectionBehavior ( QAbstractItemView::SelectRows );
tMainGrid->setSelectionMode ( QAbstractItemView::SingleSelection );

tMainGrid->verticalHeader()->setDefaultSectionSize ( 20 );
tMainGrid->verticalHeader()->setMinimumSectionSize ( 20 );
tMainGrid->horizontalHeader()->setDefaultSectionSize ( 120 );
tMainGrid->horizontalHeader()->setMinimumSectionSize ( 20 );

// FOOTER : Summary @ column footers
QHeaderView * tFooter = new QHeaderView ( Qt::Horizontal, this );
tFooter->setObjectName ( QString::fromUtf8 ( MTable_FOOTER ) );
tFooter->setMinimumHeight ( 20 );
tFooter->setMaximumHeight ( 20 );
QSizePolicy spFooter ( QSizePolicy::Expanding, QSizePolicy::Fixed );
spFooter.setHorizontalStretch(0);
spFooter.setVerticalStretch(0);
tFooter->setSizePolicy ( spFooter );
vboxLayout->addWidget ( tFooter );

// connections
connect ( tMainGrid->horizontalHeader(), SIGNAL(sectionResized(int,int,int)) ,
tTopBand, SLOT(resizeSection(int,int,int)));
connect ( tMainGrid->horizontalHeader(), SIGNAL(sectionResized(int,int,int)) ,
tFooter , SLOT(resizeSection(int,int,int)));
}


I only need that the improved table acts as a single widget, allowing the final developer to easily create layouts with the designer & then having a richer table in his project.


Why not try another approach - simply insert a second header view in the layout of the view just under the viewport?


Well. I'm working this way because I'm creating a complex framework that loads .ui files, changing QTableView elements by "MTableView" ( my class ). I use inside my app this class and I can assume has an smaller interface.


You can always hide them...

Why I must hide a compoenent I'm fighting to show ? I want to show this :

- 1 horizontal "band header" ( each band contains 1 or more columns grouped ) BEFORE the table header
- The QTableView itself ( column headers & data )
- 1 table footer, for summarys. I can assume the scroll bar is between data & footer, by now it's not a problem.


I'm not sure (you can verify that by looking into Qt sources), but I think you should calculate section sizes yourself according to the width of columns.


OK, I agree too, but I must receive some signals when column header change thier size, and I don't receive nothing.



- How can I handle the propagation of grid's column resizing to band header & footer. And of course, band resizing must affect table header & footer...

Signals & slots again...


OK, but which signals ?? I've tried some combinations and I'm receiving no one...

Thanks

Glitch
18th December 2006, 20:49
For the features you want take a look at QicsTable by ICS. Its a source licensed commercial product, but it will save you a lot of time. Its stupports all the features you mention and its made for subclassing and extension.

http://www.ics.com/support/docs/qicstableapi/index.html

www.ics.com/qt

--Justin

wysota
18th December 2006, 22:41
PIMPLs ?? What do you mean.
Private implementations. All the Q***Private classes.


I've looked inside and it's difficult, so it's my last option... ( but you can never say is impossible... )
I can say it's impossible, PIMPLs are not part of the public API, so you'd have to embed half of Qt code into your widget and I don't think you want a widget with 3MB legacy in it :)


I disagree. I don't need ALL the interface. I f you do something like this, you have all the interface, but are allways maintaining it as a single Widget :

Well said - a widget, but not as a view. The view API is not part of the public API of the widget, so you can't make simple signal-slot connections or use the widget as a custom widget in Designer without messing around with its internals.


I only need that the improved table acts as a single widget, allowing the final developer to easily create layouts with the designer & then having a richer table in his project.
As written above, the widget will not have the API of a view, so the final developer will only have access to its QWidget API from the Designer (unless you forward all the view functionality to the widget's public API practically reimplementing the view architecture, as I mentioned in the previous post).


Why I must hide a compoenent I'm fighting to show ?
I understood you want to replace the original header view with your own header and footer. Hiding the original header would be a simple way to get rid of it. Maybe I misunderstood you.



OK, I agree too, but I must receive some signals when column header change thier size, and I don't receive nothing.

OK, but which signals ?? I've tried some combinations and I'm receiving no one...

QHeaderView::sectionResized() is an obvious candidate...

jpujolf
19th December 2006, 13:22
Hi all,

Finally I didi it !! I want to post here, the connections and simple code I wrote to obtain the result I was asking for. If somebody has the same problem, perhaps can be a guide.



MTableView::MTableView ( QWidget * parent ) : QWidget ( parent )
{
m_LeftOffset = -12;
m_bResizing = false;

// 3 items : TOP BAND HEADER, CENTRAL GRID & BOTTOM FOOTER
QVBoxLayout * vboxLayout = new QVBoxLayout ( this );
vboxLayout->setSpacing(0);
vboxLayout->setMargin(0);
vboxLayout->setObjectName(QString::fromUtf8("vboxLayout"));

// TOP : Band headers
QHeaderView * tTopBand = new QHeaderView ( Qt::Horizontal, this );
tTopBand->setObjectName ( QString::fromUtf8 ( MTable_TOPBAND ) );
tTopBand->setMinimumHeight ( 20 );
tTopBand->setMaximumHeight ( 20 );
tTopBand->setOffset ( m_LeftOffset );
QSizePolicy spTop ( QSizePolicy::Expanding, QSizePolicy::Fixed );
spTop.setHorizontalStretch(0);
spTop.setVerticalStretch(0);
tTopBand->setSizePolicy ( spTop );
vboxLayout->addWidget ( tTopBand );

// CENTRAL : Data & column headers
QTableView * tMainGrid = new QTableView ( this );
tMainGrid->setObjectName ( QString::fromUtf8 ( MTable_CENTRAL ) );
QSizePolicy spCentral ( QSizePolicy::Expanding, QSizePolicy::Expanding );
spCentral.setHorizontalStretch(0);
spCentral.setVerticalStretch(0);
tMainGrid->setSizePolicy ( spCentral );
vboxLayout->addWidget ( tMainGrid );

tMainGrid->setSelectionBehavior ( QAbstractItemView::SelectRows );
tMainGrid->setSelectionMode ( QAbstractItemView::SingleSelection );
tMainGrid->verticalHeader()->setDefaultSectionSize ( 20 );
tMainGrid->verticalHeader()->setMinimumSectionSize ( 20 );
tMainGrid->horizontalHeader()->setDefaultSectionSize ( 120 );
tMainGrid->horizontalHeader()->setMinimumSectionSize ( 20 );
tMainGrid->setVerticalScrollMode ( QAbstractItemView::ScrollPerPixel );

// FOOTER : Summay @ column footers
QHeaderView * tFooter = new QHeaderView ( Qt::Horizontal, this );
tFooter->setObjectName ( QString::fromUtf8 ( MTable_FOOTER ) );
tFooter->setMinimumHeight ( 20 );
tFooter->setMaximumHeight ( 20 );
tFooter->setOffset ( m_LeftOffset );
tFooter->setResizeMode ( QHeaderView::Fixed );
QSizePolicy spFooter ( QSizePolicy::Expanding, QSizePolicy::Fixed );
spFooter.setHorizontalStretch(0);
spFooter.setVerticalStretch(0);
tFooter->setSizePolicy ( spFooter );
vboxLayout->addWidget ( tFooter );

connect ( tMainGrid->horizontalHeader(), SIGNAL(sectionResized(int,int,int)),
this, SLOT(SectionResized(int,int,int)) );
connect ( tMainGrid->horizontalHeader(), SIGNAL(sectionAutoResize(int,QHeaderView::ResizeMo de)) ,
this, SLOT(SectionAutoResize(int,QHeaderView::ResizeMode )) );

connect ( tTopBand, SIGNAL(sectionResized(int,int,int)), this, SLOT(BandSectionResized(int,int,int)) );
connect ( tMainGrid->horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(ScrollHorizontal(int)) );
}

void MTableView::BandSectionResized ( int logicalIndex, int /* oldSize */, int newSize )
{
if ( m_bResizing ) return;
m_bResizing = true;
QHeaderView * pTabHeader = this->GetMainGrid()->horizontalHeader();
pTabHeader->resizeSection ( logicalIndex, newSize );
m_bResizing = false;
}

void MTableView::SectionResized ( int logicalIndex, int /* oldSize */, int newSize )
{
QHeaderView * pHeader = qFindChild<QHeaderView *> ( this, MTable_TOPBAND );
pHeader->resizeSection ( logicalIndex, newSize );
pHeader = qFindChild<QHeaderView * > ( this, MTable_FOOTER );
pHeader->resizeSection ( logicalIndex, newSize );
}
void MTableView::SectionAutoResize ( int logicalIndex, QHeaderView::ResizeMode /* mode */ )
{
QHeaderView * pTabHeader = this->GetMainGrid()->horizontalHeader();
QHeaderView * pHeader = qFindChild<QHeaderView *> ( this, MTable_TOPBAND );
pHeader->resizeSection ( logicalIndex, pTabHeader->sectionSize ( logicalIndex ) );
pHeader = qFindChild<QHeaderView * > ( this, MTable_FOOTER );
pHeader->resizeSection ( logicalIndex, pTabHeader->sectionSize ( logicalIndex ) );
}

void MTableView::ScrollHorizontal ( int /* scrollValue */ )
{
QHeaderView * pTabHeader = this->GetMainGrid()->horizontalHeader();
QHeaderView * pHeader = qFindChild<QHeaderView *> ( this, MTable_TOPBAND );
pHeader->setOffset ( pTabHeader->offset() + m_LeftOffset );
pHeader = qFindChild<QHeaderView * > ( this, MTable_FOOTER );
pHeader->setOffset ( pTabHeader->offset() + m_LeftOffset );
}


And that's all, I have now a widget that simulates the grid I needed. I can access programmatically to the "real" grid, the band header & the footer, but all the stuff for resizing/ scrolling automation is done. That was my problem.

I think that complex problems must be solved at morning. After lunch, the brain becomes a bit lazy :p.

Now I have to make little changes to span "header band" columns and take m_LeftOffset value directly from table ( this value is, by now, hard coded, but could change ).

When I finish all the stuff, I will attach an screenshot, to show you the results. Cheers !!

Jordi.

jpujolf
9th May 2007, 17:49
The promised Screenshot of improved table.

pinktroll
10th November 2007, 17:19
hi jpujolf,

that looks good. Could you show more code of the class?
I know its some time ago since you posted your work here.

thank you in advance!

doberkofler
11th April 2010, 19:00
Well said - a widget, but not as a view. The view API is not part of the public API of the widget, so you can't make simple signal-slot connections or use the widget as a custom widget in Designer without messing around with its internals.
As written above, the widget will not have the API of a view, so the final developer will only have access to its QWidget API from the Designer (unless you forward all the view functionality to the widget's public API practically reimplementing the view architecture, as I mentioned in the previous post).
QHeaderView::sectionResized() is an obvious candidate...

What are then the alternatives to implement a footer row?

stefanadelbert
15th April 2010, 07:50
HierarchicalHeaderView 1.3.2 (http://qt-apps.org/content/show.php/HierarchicalHeaderView?content=103154&PHPSESSID=7b3458f809ad9ddc864be6a4d2eabfaa) might be worth a look. I haven't used it or looked a the code though - just came across it and remembered this post.

doberkofler
17th April 2010, 08:05
HierarchicalHeaderView 1.3.2 (http://qt-apps.org/content/show.php/HierarchicalHeaderView?content=103154&PHPSESSID=7b3458f809ad9ddc864be6a4d2eabfaa) might be worth a look. I haven't used it or looked a the code though - just came across it and remembered this post.

I checked it and as far as I can see it does not cover how to implement a footer line.

wysota
17th April 2010, 09:45
You have to place the footer manually in the view. QAbstractScrollArea::setViewportMargins() can be helpful with doing that.

doberkofler
17th April 2010, 09:48
You have to place the footer manually in the view. QAbstractScrollArea::setViewportMargins() can be helpful with doing that.

Is there any code example available on how to do this?

wysota
17th April 2010, 10:15
Is there any code example available on how to do this?

I'll try to come up with an example later today.