PDA

View Full Version : QTableView printing



derrickbj
26th September 2006, 17:09
Greetings!

I'm trying to print the contents of a QTableView to the printer (this is my first QT program sending anything to the printer). I didn't know if there was a way as easy as QTextEdit->document()->print(&printer). The closeest I could find was implementing something similar to what I saw in Qt's Pixelator example, but all this seems to do for me is spit out an empty sheet of paper:

Note: 'model' is previously defined as QAbstractItemModel *model



QPrinter printer(QPrinter::HighResolution);

QPrintDialog *dlg = new QPrintDialog(&printer, this);
dlg->setWindowTitle(tr("Print Table"));

if (dlg->exec() != QDialog::Accepted)
return;

QPainter painter;
painter.begin(&printer);

int rows = model->rowCount(QModelIndex());
int columns = model->columnCount(QModelIndex());

double xscale = printer.pageRect().width();
double yscale = printer.pageRect().height();
double scale = qMin(xscale, yscale);

painter.translate(printer.paperRect().x() + printer.pageRect().width()/2,
printer.paperRect().y() + printer.pageRect().height()/2);
painter.scale(scale, scale);
QModelIndex parent = QModelIndex();
painter.save();
QStyleOptionViewItem option;

const int ItemSize = 256;
float x = ItemSize /2;
for (int printRow = 0; printRow < rows; ++printRow) {
float y = ItemSize /2;
for (int column = 0; column < columns; ++column) {
option.rect = QRect(int(x), int(y), ItemSize, ItemSize);
myTableView->itemDelegate()->paint(&painter, option, model->index(printRow, column, parent));
x = x + 256;
}
y = y + 256;
}
painter.restore();
painter.end();

Any ideas would be greatly appreciated. The only thing I can think of is creating a QTextDocument() and feeding it the QTableView in HTML format and then printing that.

Thanks,
--D

jpn
26th September 2006, 17:28
At least you'll need a proper QStyleOptionViewItem for the delegate to function properly.
Try taking advantage of QAbstractItemView::viewOptions().

PS. use [ code ] -tags to make code blocks more readable.

derrickbj
26th September 2006, 18:29
Thanks for the reply and the tip!! I guess I'm a little confused, though. When I try to do QStyleOptionViewItem option = QAbstractItemView::viewOptions(), compiler tells me I need to state an object. So when I do QStyleOptionViewItem option = myTableView->viewOptions, compiler states I can't b/c viewOptions is protected. What am I missing??

Thanks,
--D

jpn
26th September 2006, 19:25
Yes, it's a non-static and a protected method. So for this kind of approach to work you would have to move some parts of the printing code to a QTableView subclass.

derrickbj
26th September 2006, 20:27
Do you have any examples to go on? I guess I'm unclear of how to get my existing QTableView's contents into a separate QTableView subclass - or are you saying I should subclass my QTableView alltogether?

Thanks!

derrickbj
29th September 2006, 18:56
Ok, I finally opened my eyes and saw how to get the TableView contents from the view to my subclass (again, model predefined as QAbstractItemModel *model, and mainTableView->setModel(model) happens during MainWindow construction) but I'm back to square 1 - All that prints is a blank page. I know setting the PrintTableView's model to 'model' passes the contents of the mainTableView to tempTableView b/c I can display the contents of the indexes through qDebug() in PrintTableView::printTable(). Is there something more I need to do to viewOptions()? or is there something else I'm missing?

Thanks!!



void MainWindow::Print()
{
QPrinter printer(QPrinter::HighResolution);
QPrintDialog *dlg = new QPrintDialog(&printer, this);
dlg->setWindowTitle(tr("Print ACLs"));

if (dlg->exec() != QDialog::Accepted)
return;

PrintTableView *tempTableView = new PrintTableView();
tempTableView->setModel(model);
tempTableView->printTable(printer);
}

PrintTableView::PrintTableView(QWidget *parent) : QTableView (parent)
{

}

void PrintTableView::printTable(QPrinter &printer)
{
QString str;
QPainter painter;
painter.begin(&printer);
const int ItemSize = 256;
int rows = model()->rowCount(QModelIndex());
int columns = model()->columnCount(QModelIndex());
int sourceWidth = (columns+1) * ItemSize;
int sourceHeight = (rows+1) * ItemSize;
painter.save();

double xscale = printer.pageRect().width();
double yscale = printer.pageRect().height();
double scale = qMin(xscale, yscale);

painter.translate(printer.paperRect().x() + printer.pageRect().width()/2,
printer.paperRect().y() + printer.pageRect().height()/2);
painter.scale(scale, scale);
painter.translate(-sourceWidth/2, -sourceHeight/2);

QStyleOptionViewItem option = viewOptions();

float x = ItemSize /2;
for (int printRow = 0; printRow < rows; ++printRow) {
float y = ItemSize /2;
for (int column = 0; column < columns; ++column) {
option.rect = QRect(int(x), int(y), ItemSize, ItemSize);
itemDelegate()->paint(&painter, option, model()->index(printRow, column, QModelIndex()));
x = x + 256;
}
y = y + 256;
}
painter.restore();
painter.end();
}

QStyleOptionViewItem PrintTableView::viewOptions() const
{
QStyleOptionViewItem option = QAbstractItemView->viewOptions();
return option;
}

jpn
29th September 2006, 19:34
The painter's scale factors don't look proper. They should be pretty close to 1.0..



double xscale = printer.pageRect().width();
double yscale = printer.pageRect().height();
double scale = qMin(xscale, yscale);
...
painter.scale(scale, scale); // causes painter to scale it's result width/height times bigger


I guess it should be something more like:


double xscale = printer.pageRect().width() / width();
double yscale = printer.pageRect().height() / height();
...
painter.scale(xscale, yscale);

derrickbj
29th September 2006, 19:56
J-P,

Thanks for the reply. I implemented your suggestion, and I'm still getting a blank page. :(

jpn
29th September 2006, 21:02
void TableView::print(QPainter* painter, const QRect& area)
{
const int rows = model()->rowCount();
const int cols = model()->columnCount();

// calculate the total width/height table would need without scaling
double totalWidth = 0.0;
for (int c = 0; c < cols; ++c)
{
totalWidth += columnWidth(c);
}
double totalHeight = 0.0;
for (int r = 0; r < rows; ++r)
{
totalHeight += rowHeight(r);
}

// calculate proper scale factors
const double scaleX = area.width() / totalWidth;
const double scaleY = area.height() / totalHeight;
painter->scale(scaleX, scaleY);

// paint cells
for (int r = 0; r < rows; ++r)
{
for (int c = 0; c < cols; ++c)
{
QModelIndex idx = model()->index(r, c);
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(idx);
itemDelegate()->paint(painter, option, idx);
}
}
}

// printer usage
QPainter painter(&printer);
tableView->print(&painter, printer.pageRect());

// test on pixmap
QPixmap pixmap(320, 240);
QPainter painter(&pixmap);
tableView->print(&painter, pixmap.rect());
pixmap.save("table.png", "PNG");


Edit: I forgot to add painter translating but that should be trivial to add once you get the idea..

jpn
29th September 2006, 21:43
Yet another (maybe even better?) option is to use QPainter's powerful redirecting mechanism. This approach doesn't even require subclassing of QTableView.



QPrinter printer(QPrinter::HighResolution);
QPrintDialog dlg(&printer, this);
if (dlg.exec() == QDialog::Accepted)
{
// calculate the total width/height table would need without scaling
const int rows = table->model()->rowCount();
const int cols = table->model()->columnCount();
double totalWidth = 0.0;
for (int c = 0; c < cols; ++c)
{
totalWidth += table->columnWidth(c);
}
double totalHeight = 0.0;
for (int r = 0; r < rows; ++r)
{
totalHeight += table->rowHeight(r);
}

// redirect table's painting on a pixmap
QPixmap pixmap(totalWidth, totalHeight);
QPainter::setRedirected(table->viewport(), &pixmap);
QPaintEvent event(QRect(0, 0, totalWidth, totalHeight));
QApplication::sendEvent(table->viewport(), &event);
QPainter::restoreRedirected(table->viewport());

// print scaled pixmap
QPainter painter(&printer);
painter.drawPixmap(printer.pageRect(), pixmap, pixmap.rect());
}

derrickbj
2nd October 2006, 03:54
J-P,

This is AWESOME!!! Thank you so much! both of these work well. The first suggestion works nicely and scales the text well. The 2nd option prints only to the printer (i.e., won't print to Adobe PDF's printer - for instance, i get a "Critical: QPixmap::toWinHBITMAP(), failed to create dibsection(Not enough stoarge is available to process this command.) but that's a hurdle to tackle another time) but prints the grid lines, which is REALLY nice. One issue I have with both suggestions, however, is that for large tables (more than 1 page), it scales the text to fit onto one page. I had thought to emit a signal to call QPrinter::newPage(), but, I can't seem to do this with the QPainter you've set up.

I've also tried :



double totalHeight = 0.0;
for (int r = 0; r < 45; ++r){
totalHeight += table->rowHeight(r);
}


for your examples, but this only prints 1 page as well. Do you know how I can tell QPainter to send a new page request to the Printer?

Again...Thank you very much for your help!!!

--D

jpn
2nd October 2006, 05:29
I suppose you have two options. Either you will have to do some calculation before hand so that you know how many rows and columns fit to one page and then request the table to paint a certain range of rows and columns to the specified area. Another option is to pass the whole QPrinter instead of just QPainter and do the calculations inside table's printing method where QPrinter::newPage() is invoked when appropriate.

derrickbj
9th October 2006, 18:22
J-P,

I took your 2nd option. I figured that 45 rows comfortably fit on a page. And here's what I have for the table's print method.



void PrintTableView::printTable(QPrinter* printer, QPainter* painter, const QRect& area)
{
const int rows = model()->rowCount();
const int columns = model()->columnCount();
double totalWidth = 0.0;
double totalPageHeight = 0.0;
double totalHeight = 0.0;
for (int c = 0; c < columns; ++c)
{
totalWidth += columnWidth(c);
}

for (int p = 0; p < 45; ++p)
{
totalPageHeight += rowHeight(p);
}

for (int r = 0; r<rows; ++r)
{
totalHeight += rowHeight(r);
}

const double xscale = area.width() / totalWidth;
const double yscale = area.height() / totalHeight;
const double pscale = area.height() / totalPageHeight;

painter->scale(xscale, yscale);
painter->translate(area.x() + xscale, area.y() + pscale);

bool ok;
int x=0;

for (int r = 0; r < rows; ++r) {
++x;
for (int c = 0; c < columns; ++c) {
QModelIndex idx = model()->index(r,c);
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(idx);
itemDelegate()->paint(painter, option, idx);

}
if (x == 45){
ok = printer->newPage();
x = 0;
}
}
}


What winds up happening is that if I have the following:


painter->scale(xscale, yscale);
painter->translate(area.x() + xscale, area.y() + pscale);

this prints out fine exept the text is EXTREMELY small. When I swap pscale and yscale, as in:


painter->scale(xscale, pscale);
painter->translate(area.x() + xscale, area.y() + yscale);


the first page prints out fine (rows 1 - 45), and then the printer spits out x more blank pages to cover the rest of the rows. Do I need to re-establish the QStyleOptionViewItem for every newPage()? I'm really puzzled why the printing works with when scaled really small, but when scaled to 45 rows per page, only the first page contains text. Any ideas?

Thanks!
--Derrick

jpn
9th October 2006, 20:14
Maybe you need to save the QPainter always before scaling and translating and restore it afterwards. You are using the same QPainter object for painting all pages, this means you are also scaling it again and again.

QPainter::save()
QPainter::restore()

derrickbj
9th October 2006, 23:17
hmmm..I'm not exactly sure. Here's what I tried based on your idea...



void PrintTableView::printTable(QPrinter* printer, QPainter* painter, const QRect& area)
{
const int rows = model()->rowCount();
const int columns = model()->columnCount();

double totalWidth = 0.0;
double totalPageHeight = 0.0;
double totalHeight = 0.0;
for (int c = 0; c < columns; ++c)
{
totalWidth += columnWidth(c);
}

for (int p = 0; p < 45; ++p)
{
totalPageHeight += rowHeight(p);
}

for (int r = 0; r<rows; ++r)
{
totalHeight += rowHeight(r);
}

const double xscale = area.width() / totalWidth;
const double yscale = area.height() / totalHeight;
const double pscale = area.height() / totalPageHeight;

painter->scale(xscale, yscale);
painter->translate(area.x() + xscale, area.y() + pscale);
painter->save();

bool ok;
int x=0;

for (int r = 0; r < rows; ++r) {
++x;
for (int c = 0; c < columns; ++c) {
QModelIndex idx = model()->index(r,c);
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(idx);
itemDelegate()->paint(painter, option, idx);
}
if (x == 45)
{
ok = printer->newPage();
x = 0;
painter->restore();
}
}
}


if I put painter->save() before the painter->scale(), it prints every page, but only in a very very small square. if I leave it the way it is shown here, it prints every page stretched to page width, but compressed to 1/8th the page. If I do painter->scale(xscale, pscale),and leave painter->save() after painter->translate(), then it prints the first page correctly, but all other pages are blank. I tried re-scaling the painter when x == 45, but that only hangs the program. :(

jpn
10th October 2006, 07:24
Ok, sorry for not paying enough attention. Forget about saving and restoring for now. Seems I didn't read your code carefully enough. I somehow thought you were calling the print function for every page... :)

What I'm suspecting is that the areas returned by the visualRect(idx) don't match on the second page because it's not aware of that you have requested a new page and the coordinates have been reseted. So you would have to translate the painter in vertical direction after every new page request. Try to print the pageRect and the visualRects in the debug output or something and you'll see.

derrickbj
10th October 2006, 15:31
J-P,

You have NOTHING to apologize for!! You've been very helpful.

That's EXACTLY what needed to be done!!! I ran visualRect through qDebug() after every page and found that y() was incrementing by 1350. I also found out that after printing 45 rows, option.rect.x() was 1000 and not 0. So, I did painter->translate(0, -1350) after calling printer->newPage() and everything prints beautifully. I can't thank you enough for your help!! I was really appreciate it! :D



void PrintTableView::printTable(QPrinter* printer, QPainter* painter, const QRect& area)
{
const int rows = model()->rowCount();
const int columns = model()->columnCount();
double totalWidth = 0.0;
double totalPageHeight = 0.0;
double totalHeight = 0.0;
for (int c = 0; c < columns; ++c)
{
totalWidth += columnWidth(c);
}

for (int p = 0; p < 45; ++p)
{
totalPageHeight += rowHeight(p);
}

for (int r = 0; r<rows; ++r)
{
totalHeight += rowHeight(r);
}

const double xscale = area.width() / totalWidth;
const double yscale = area.height() / totalHeight;
const double pscale = area.height() / totalPageHeight;
painter->scale(xscale, pscale);
painter->translate(area.x() + xscale, area.y() + yscale);

bool ok;
int x=0;
QStyleOptionViewItem option;

for (int r = 0; r < rows; ++r) {
++x;
for (int c = 0; c < columns; ++c) {
QModelIndex idx = model()->index(r,c);
option = viewOptions();
option.rect = visualRect(idx);
itemDelegate()->paint(painter, option, idx);
}
if (x == 45)
{
ok = printer->newPage();
x=0;
painter->translate(0, -1350);
}
}
}

xelag
16th December 2006, 00:56
I don't understand what to do before calling the function and what to set before for the three arguments :

void PrintTableView::printTable(QPrinter* printer, QPainter* painter, const QRect& area)

Do we call it from the mainwindow?

What to do to print? Where is the line to print our QTableView? As the line used in the post from jpn the 29th September 2006 22:43 with :


//[...]
painter.drawPixmap( const QRect & target, const QPixmap & pixmap, const QRect & source )
//[...]

Can you put the entire code on-line please with what to be done before and after the function printTable to print a QTableWidget. I am beginner with Qt and i need some full code example...

Thank you for your help.
Alex

mandapally
6th February 2007, 11:48
{Two posts merged}

Hi All,

Iam Jagannath Mandapally

Hi All,

My Requirement is Print table , Means Generating .ps file and giving to APS Print but I have some issues.

1) Preview is not visible on APS Print

2) Printing table but ignoring some rows

3) Grid lines are not Printing

my code is :


void TableEditor::printt()
{

QPrinter printer(QPrinter::HighResolution);
//printer.setFullPage(true);
//printer.setPageSize(QPrinter::A4);
/*int rightMargin = printer.paperRect().right() - printer.pageRect().right();
int bottomMargin = printer.paperRect().bottom() - printer.pageRect().bottom();
int leftMargin = printer.paperRect().left() - printer.pageRect().left();
int topMargin = printer.paperRect().top() - printer.pageRect().top();*/ //This is commented
//printer.margins(&topMargin,&leftMargin,&bottomMargin, &rightMargin);

printer.setOutputFileName("rtest.ps");
QPrintDialog dlg(&printer, this);
double totalWidth = 0.0;
double totalHeight = 0.0;
double totalPageHeight=0.0;
//QRect area;
if (dlg.exec() == QDialog::Accepted){
const int rows=view->model()->rowCount();
const int cols = view->model()->columnCount();
printf("no of rows\n %d",rows);
printf("\n no of columns %d",cols);
for (int c = 0; c < cols; ++c) {
totalWidth += view->columnWidth(c); }
for(int p=0; p<45; p++) //45
{
totalPageHeight+= view->rowHeight(p);


}
for (int r = 0; r < rows; ++r)
{ totalHeight += view->rowHeight(r); }


} //if loop ending here

QPainter painter(&printer);
//QPainter::setRedirected(view->viewport(), &painter);
//QPaintEvent event(QRect(0, 0, totalWidth, totalHeight));
//view->paintEvent(&event);
//painter.drawLines(0,0,rows,cols);
//QApplication::sendEvent(view->viewport(), &event);
//QPainter::restoreRedirected(view->viewport()); //This is commented for testing row nums
//QPainter painter(&printer);
painter.begin(&printer);
const int rows=view->model()->rowCount();
const int columns=view->model()->columnCount();
printf("The num of rows are %d",rows);
QRect area = printer.paperRect(); // here should be pageRect
const double xscale = area.width() / totalWidth;
const double yscale = area.height() / totalHeight;
const double pscale = area.height() / totalPageHeight;
painter.scale(xscale+9, yscale+9); // With the scaling 3 It is Printing all
//painter.translate(area.x() + xscale, area.y() + pscale); //This is original
painter.translate(area.x() + xscale, area.y() + yscale);
//painter.save(); //commented
int x=0;
//int y=0 ;// This is introduced for the columns
//view->paintEvent(&event);
QPainter paint(this);
paint.setPen(Qt::red);
paint.drawRect(0, 0, 0, 0);

QStyleOptionViewItem option;

for (int r=0; r<rows; r++)
{
++x;

for(int c=0; c<columns; c++)
{
//++y;


/*double xscale = printer.pageRect().width() / width();
double yscale = printer.pageRect().height() / height();
double scale = qMin(xscale, yscale);
QRect rect = painter.viewport( ); */
//QSize size = pixmap.size( );
//size.scale( rect.size( ), Qt::KeepAspectRatio );
//painter.drawLines(0,0,rows,columns);


QModelIndex idx = view->model()->index(r,c);
option = view->viewOptions();
option.rect =view->visualRect(idx);
view->itemDelegate()->paint(&painter, option, idx);
// painter.drawPixmap(printer.pageRect(), pixmap, pixmap.rect());

// painter.drawPixmap((r,c), pixmap, pixmap.rect());

// if(page!=printer.numCopies())
} //columns are closed here
if (x==39)
{
printf("This is inside if loop %d:",x);
printer.newPage();
x=0;
//y=0;
painter.translate(0, -1150);
//painter.translate(0,-1000);
//painter.translate(0,-850);
x=0;

// page++
//painter.drawPixmap(printer.pageRect(), pixmap, pixmap.rect());

//x=0;
painter.save();
painter.restore();

// break;
}

printf("%d",rows);


// }
}
painter.end();
system("apsprint rtest.ps");




}

jacek
6th February 2007, 17:27
Next time, please, use [code] tags, format your code properly and remove unnecessary lines.

mandapally
9th February 2007, 08:25
Hi Jacek,

Thank U very much , I need solution desparately

BR
Jagannath

jacek
9th February 2007, 11:22
First try cleaning your code, because it's unreadable.

Maybe you should consider creating a report in HTML using QTextDocument?

baray98
23rd June 2010, 07:57
This was very helpful to my Problem with Printing from QTableModel but I have encountered some hick ups

when i print a cell i use this


//i have initialized my painter like this
QPainter painter (printer);
painter.scale(idealXScale, idealYScale);
painter.setClipping(true);
qDebug() << "hasClippings = "<< painter.hasClipping(); // this will always give me false


then i pass painter on to print a page



for (int r = info.startRow; r <= info.endRow; ++r)
{
for (int c = info.startCol; c <= info.endCol; ++c)
{
QModelIndex idx = model()->index(r,c);
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(idx);
if (r % 2 == 0)
{
QBrush brush(QColor(220, 220, 220), Qt::SolidPattern);
painter.fillRect(option.rect, brush);
}

itemDelegate()->paint(&painter, option, idx);
}
}


now when the cell's content is clipped ( content not fully shown in the rect or maybe elided) then somehow the text will be printed but text is too big.

picture speaks a thousand words

iraj_jelo
22nd May 2012, 14:07
hi friend . how i do translate this part code from c++ to python?




for (int c = 0; c < cols; ++c)
{
QModelIndex idx = model()->index(r, c);
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(idx);
itemDelegate()->paint(painter, option, idx);
}

iraj_jelo
23rd May 2012, 08:57
Visit Here (pyqt printpreview QTableView )
(http://www.qtcentre.org/threads/49090-pyqt-printpreview-QTableView)