QTableView issue with resizeEvent() - c++

I have an object that inherits QTableView and overrides the resizeEvent() method to set the widths of the table columns to a percantage of the available space if the table as a whole is resized.
Code is as follows:
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
// Call our base resizeEvent to handle the vertical resizing which
// we don't bother with here
QTableView::resizeEvent(ev);
}
This all works fine until the user manually resizes one of the columns and stretches it beyond the viewport (bringing up the horizontal scrollbar). This then triggers my resizeEvent() call which resets the column widths to the percentage defaults.
I can overcome this issue by connecting to the sectionResized() signal on the table header and setting a timer running. If the resizeEvent() is called whilst the timer is active then I don't recalculate the table widths.
Code below:
connect(horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(slotSectionResized(int, int, int)));
void DDUTableView::slotSectionResized(int /*logicalIndex*/, int /*oldSize*/, int /*newSize*/)
{
timer_->start(500);
}
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
if (timer_->isActive()) {
return;
}
// etc
This works but is messy. Anyway the simple question is can I prevent the resizeEvent() from being called if the user manually adjusts the column headers beyond the scope of the viewport? Alternatively, if not, is it possible to identify in the resizeEvent() whether this particular situation has occurred without having to set timers and the like?

Checking if the scrollbar is visible worked for my scenario.
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
if (!horizontalScrollBar()->isVisible()) {
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
}
// Call our base resizeEvent to handle the vertical resizing which
// we don't bother with here
QTableView::resizeEvent(ev);
}

Related

Stopping the VerticalHeader to update on QTableview

I am implementing a QTableview to display a huge amount of data. Instead of showing all at once, I am showing 10000 rows and dynamically adding and subtracting the data from the table. I am facing a problem when deleting the upper rows; the vertical header gets updated, and row numbers get changed. Any way to stop the vertical header from updating.
bool flag = 0;
int last_row = 1000;
int top_row = 0;
int margin = 30;
void DataTable::verticalScrollbarAction(int action) {
if(((this->rowAt(0)) - margin) < top_row && flag == 1)
{
this->getTableModel()->GetData(top_row-101, top_row-1, 0);
this->getTableModel()->removeRows(last_row -101, last_row -1);
last_row -= 100;
top_row -= 100;
}
else if ((this->rowAt(this->height()) + margin) > last_row)
{
flag = 1;
this->getTableModel()->GetData(last_row, last_row+100, 1);
this->getTableModel()->removeRows(0, 100);
last_row += 100;
top_row += 100;
}
}
The below answer is just my guess.
May be you can try a bit on it.
Get the vertical header object
QHeaderView *pHeaderView = yourTableView->verticalHeader();
Now go through the below documentation defaultSectionSize of QHeaderView
https://doc.qt.io/qt-6/qheaderview.html#defaultSectionSize-prop
By default, the value of this property is style dependent. Thus, when
the style changes, this property updates from it. Calling
setDefaultSectionSize() stops the updates, calling
resetDefaultSectionSize() will restore default behavior.
May be what you can do is first call
pHeaderView->setDefaultSectionSize(QHeaderView::Fixed);
After you are done with your business, you can call below to restore default behavior.
pHeaderView->resetDefaultSectionSize();

Fixed Aspect Ratio For Qt Windows

I have read many questions about this topic but all I have found are either unanswered, do not do what I want, or simply do not work.
My problem is that of maintaining a height to width ratio while resizing my main window. So far I have seen propositions to override 'QLayout' and use 'heightForWidth' to perform the deed, to use the 'resizeEvent' call back to change the size hint, and to use the 'resizeEvent' call back to resize the window.
The first two options I mentioned only work in the examples for dragging width; I want a user to be able to drag width or height. The second of the two I attempted to adapt for my purposes but it causes my code to crash (the first relies on the 'heightForWidth' function and I cannot find a 'widthForHeight' function {The comments in this thread support my conclusion: How to maintain widgets aspect ratio in Qt?}). The third option was what I originally attempted but has a lot of bugs: most of the time the window snaps back to its original size, having rapidly transitioned between that and where it should be while dragging the border with the mouse.
The attempt which causes a crash:
void window::resizeEvent(QResizeEvent *event)
{
//window::resizeEvent(event); causes crash
if ((this->width() * 5 / 8) > (this->height() + 1)
|| (this->width() * 5 / 8) < (this->height() - 1))
{
updateGeometry();
}
}
QSize window::sizeHint() const
{
QSize s = size();
if (lastWidth != s.width())
{
s.setHeight((s.width()*5)/8);
//s.setWidth(window::sizeHint().width());
}
else if (lastHeight != s.height())
{
s.setWidth((s.height()*8)/5);
//s.setHeight(window::sizeHint().height());
}
return s;
}
The attempt which has bugs:
void window::resizeEvent(QResizeEvent *)
{
//If the window does not have the correct aspect ratio
//This should be false if this function caused the resize event,
// preventing a loop
if ((this->width() * 5 / 8) > (this->height() + 1)
|| (this->width() * 5 / 8) < (this->height() - 1))
{
int currentHeight = this->height();
int currentWidth = this->width();
//Change the dimension the user did not to match the user's change.
if (lastWidth != currentWidth)
{
lastWidth = currentWidth;
currentHeight = currentWidth * 5/8;
lastHeight = currentHeight;
}
else
{
lastHeight = currentHeight;
currentWidth = currentHeight * 8/5;
lastWidth = currentWidth;
}
//update the change
this->resize(currentWidth,currentHeight);
}
}

QTableView resize vertically data not refreshed

I've implemented a table through deriving QTableView and QAbstractTableModel. It all seems to work fine except when I resize the table vertically the rows that were originally out of view don't show any data.
There is no issue when resizing horizontally possibly because I've overridden the resizeEvent() method and am recalculating column widths which I obviously don't do if the table is resized vertically.
I'm using the following code in the model to add data to the table:
bool DDUTableModel::insertRow(int row, const QModelIndex& parent)
{
beginInsertRows(parent, row, row);
digital_display_list_.append(DigitalDisplayData(path_));
endInsertRows();
return true;
}
The resizeEvent() looks like this:
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
}
Any ideas?
The problem was with the resizeEvent(). I need to also invoke the method in the QTableView class that I derived from to force a refresh on vertical resizing. Amended method looks like this:
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
QTableView::resizeEvent(ev);
}

QTableView slow performance with 1000s of visible cells

I'm using QTableView in Qt 4.8.4 to visualize a lot of data (large/many protein amino acid sequences) and I'd like to be able to make cells as small as possible so I can pack as many as possible into a given window. The problem I'm running into is that when there are many cells displayed at once, everything (e.g. scrolling, resizing, and in general repainting) slows down to a crawl. Here's some sample code (adapted from the examples/tutorials/1_readonly tutorial):
MyModel::MyModel(QObject *parent):QAbstractTableModel(parent){}
int MyModel::rowCount(const QModelIndex & /*parent*/) const {
return 200;
}
int MyModel::columnCount(const QModelIndex & /*parent*/) const {
return 60;
}
QVariant MyModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole){
return QString("%1").arg(index.row()%10);
}
return QVariant();
}
and here's the code which runs the table view:
int main(int argc, char *argv[]){
QApplication a(argc, argv);
QTableView tableView;
tableView.horizontalHeader()->setDefaultSectionSize(15);
tableView.verticalHeader()->setDefaultSectionSize(15);
tableView.setFont(QFont("Courier",12));
MyModel myModel(0);
tableView.setModel( &myModel );
tableView.setGeometry(0,0,1000,1000);
tableView.show();
return a.exec();
}
When I use Instruments on OSX while scrolling up and down, it's spending a lot of time in QWidgetPrivate::drawWidget and down the stack, QWidgetPrivate::paintSiblingsRecursive... i.e., it's spending a lot of time redrawing my table.
I'm new to Qt, so I'm not sure how to approach this problem. Should I:
override the paint method? i.e. perhaps I could save my whole table as an image, and when scrolling happens, to just repaint the image until movement stops (and then return to painting the table directly)?
Not use tables in Qt at all? Perhaps I can just use a Text field to accomplish my purposes? e.g. for each letter in the text, i'd like hovertext, selections, coloring letter's backgrounds, etc.
Both of these options seem like a lot of work to make up for ground lost by switching away from QTableView. Are there any other suggestions?
QTableView is known to be slow when dealing with large datasets. I suggest you to switch to Qt Graphics View Framework. It's much more efficient and is flexible enough to display a table.
QGraphicsScene scene;
QFont font("Courier",12);
QFontMetrics font_metrics(font);
int padding = 2;
int column_width = font_metrics.width("X") + padding * 2;
int row_height = font_metrics.height() + padding * 2;
int rows = 200, columns = 60;
for(int x = 0; x < columns; x++) {
for(int y = 0; y < rows; y++) {
QGraphicsSimpleTextItem* item = scene.addSimpleText(QString().setNum(y % 10), font);
item->setPos(x * column_width + padding, y * row_height + padding);
}
}
for(int x = 0; x < columns + 1; x++) {
int line_x = x * column_width;
scene.addLine(line_x, 0, line_x, rows * row_height)->setPen(QPen(Qt::gray));
}
for(int y = 0; y < rows + 1; y++) {
int line_y = y * row_height;
scene.addLine(0, line_y, columns * column_width, line_y)->setPen(QPen(Qt::gray));
}
QGraphicsView view(&scene);
view.resize(700, 700);
view.show();
Try to use QTreeView, but set uniformRowHeights to true. Millions of items worked last time I've checked.
EDIT: QTreeView supports tables and more!
i'm using a 1e8 rows table and had to switch to QTreeView with setUniformRowHeights(true);

Qt setColumnWidth does not work

Have written the following code:
m_selectCategoryTableWidget = new QTableWidget;
m_selectCategoryTableWidget->setRowCount(0);
m_selectCategoryTableWidget->setColumnCount(2);
m_selectCategoryTableWidget->setHorizontalHeaderLabels(QStringList()<<tr("Category")<<tr("Number of items"));
m_selectCategoryTableWidget->verticalHeader()->setVisible(false);
m_selectCategoryTableWidget->horizontalHeader()->setStretchLastSection(true);
//m_selectCategoryTableWidget->setColumnWidth(0,400);
m_selectCategoryTableWidget->resizeColumnsToContents();
m_selectCategoryTableWidget->setColumnWidth(1,100); //this does not take effect
Please help.
Well, Qt's logic is so, that after column resize, scroll bar area checks how columns fit into it. And if the sum of all columns' widths is less than the widget's visible width, then the last column gets resized to fill up the space leading to no visible result of calling setColumnWidth(). Actually two resizes happen - to shrink and reverse to enlarge.
So, the lesson is - get control's visible width, recalculate sizes as you want, and resize all but the last column. For two column case it's really simple:
int secondColumnWidth = 100;
int firstColumnWidth = m_selectCategoryTableWidget->width() - secondColumnWidth;
if (firstColumnWidth > 0)
{
m_selectCategoryTableWidget->setColumnWidth(0, firstColumnWidth);
}
else
{
m_selectCategoryTableWidget->resizeColumnsToContents();
}
Good luck!
It is also possible to specify that you want the first column to fill the remaining space instead of the last column. Unfortunately this does seem to prevent the user from being able to manually resize the columns.
int secondColumnWidth = 100;
m_selectCategoryTableWidget->header()->setStretchLastSection(false);
m_selectCategoryTableWidget->header()->setResizeMode(0, QHeaderView::Stretch);
m_selectCategoryTableWidget->setColumnWidth(1, secondColumnWidth);
This will automatically resize the columns to fit ("view" is an QTableView* and model is a QSqlQueryModel*).
static_cast<QTableView*>(view)->horizontalHeader()
->resizeSections(QHeaderView::ResizeToContents);
QFontMetrics fm(view->font());
for (int i = 0 ; i < model->record().count(); ++i)
{
int maxLength = 0;
for (int j = 0; j < model->rowCount(); ++j)
{
QString cell = model->record(j).value(i).toString();
if (fm.width(cell) > maxLength)
{
maxLength = fm.width(cell);
}
}
QHeaderView& hv = *static_cast<QTableView*>(view)->horizontalHeader();
if (maxLength > hv.sectionSize(i))
{
hv.resizeSection(i, maxLength * 1.5);
}
}