QTableWidget scroll to a previous value - qt

i've a QTableWidget.
In this QTableWidget i've loaded 500 rows (7 columns).
The user can scroll the rows through the vertical scrollbar.
I want to save the vertical scrollbar value and apply it automatically at the next startup.
What I did :
On close event i save the vertical scrollbar value :
saveToFile(myQTableWidget->verticalScrollBar()->value());
on close event, in order to debug i added this lines :
qDebug() << "Scroll Min " << myQTableWidget->verticalScrollBar()->minimum();
qDebug() << "Scroll Max " << myQTableWidget->verticalScrollBar()->maximum();
qDebug() << "Scroll Val " << myQTableWidget->verticalScrollBar()->value();
The output is :
Scroll Min 0
Scroll Max 58811
Scroll Val 34758
On startUp I read the value from file :
int scrollValue = loadFromFile(myFileName);
now in the variable scrollValue i've the value 34758.
Then I load the 500 elements in the QTableWidget and run this code :
myQTableWidget->setRowCount(500);
for(int i = 0; i < 500; i++) {
myQTableWidget->setItem(i, 0, ...);
}
//EDIT 15-11-2019
myQTableWidget->resizeRowsToContents(); // <- The guilty row
qDebug() << "Scroll Min " << myQTableWidget->verticalScrollBar()->minimum();
qDebug() << "Scroll Max " << myQTableWidget->verticalScrollBar()->maximum();
if (scrollValue > 0)
myQTableWidget->verticalScrollBar()->setValue(scrollValue);
The strange thing is that the output is different from the previous (the row number doesn't change) :
Scroll Min 0
Scroll Max 14906
1) Why the Scroll Max is different even if the QTableWidget contains exactly the same items ?
2) Is there a better way to do this ?

I had similar problems with the QTableWidget. The values of the scrollbar were not updated.
Try: myQTableWidget->update(); after inserting new items. The values of the scrollbar should fit now.

Here the solution :
myQTableWidget->setRowCount(500);
for(int i = 0; i < 500; i++) {
myQTableWidget->setItem(i, 0, ...);
}
myQTableWidget->resizeRowsToContents(); // <- The guilty row
myQTableWidget->verticalHeader()->resizeSections(QHeaderView::ResizeToContents);
QCoreApplication::processEvents();
qDebug() << "Scroll Min " << myQTableWidget->verticalScrollBar()->minimum();
qDebug() << "Scroll Max " << myQTableWidget->verticalScrollBar()->maximum();
if (scrollValue > 0)
myQTableWidget->verticalScrollBar()->setValue(scrollValue)
I added this two lines after the resizeRowsToContents() :
myQTableWidget->verticalHeader()->resizeSections(QHeaderView::ResizeToContents);
QCoreApplication::processEvents();
Now the scroll value is correctly updated.

Related

Problems with Qt iterators in QLinkedList

I know I will be asked for a minimal example, but I am not sure to be able to come up with one at all, or it won't be very minimal. But I may still learn valuable knowledge from this discussion.
So I have a series of data items of a given class organized in a QLinkedList. The user is provided with an interface that allows to navigate through this data and edit it. In particular, some of the possible editing actions require multiple entries in the QLinkedList to be modified - for example all subsequent or all previous entries. Items can also be added or removed to the list, and then edited.
In the code, I simply keep an iterator up to date that points to the current data item in the chain, called m_currentItem. I then use it to display the data at the current item, loop from the current to the end or to the beginning. I require this iterator to be valid throughout the whole execution and lifetime of the application, even if items are added or removed. When the user presses "next" or "previous" the iterator is simply incremented or decremented. When the user deletes an item in the list (except the last), the iterator then points to the next entry and its content is displayed. When a user adds an item in the list, m_currentItem is now made to point to this new item.
This is where things go pretty wrong : On those backwards or forward loops. They often (but not always ...) go over the end condition and continue into unknown data until a major crash ensues. Sometimes it also loops back to the first element in the list (!!!) and then it's an infinite loop for ya.
Here's an example taken from my code, anything obviously wrong ? Anything else I should know about Qt iterators ?
EDIT :
Just learned about reverse iterators ! It seems recommends using them instead of the -- overloaded operator on standard iterators ! But how do I make m_currentItems a reverse iterator ?
Also I read about implicit sharing ... I don't really do anything like that I don't think, but does that go to show that I shouldn't be using long-living iterators like I do ?
include <QLinkedList>
struct Member
{
int memberID;
}
struct Item
{
/* A map of members, each mapped to an ID */
QMap<int, Member> m_members;
QString name;
}
typedef QLinkedList<Item> LinkedItems;
LinkedItems m_items;
LinkedItems::iterator m_currentItem;
...
if(m_currentItem != m_items.end())
{
for(LinkedItems::iterator iter = m_currentItem+1; iter != m_items.end(); iter++)
{
// Do things on *iter
}
}
A more complex implementation : in each linked Items are found Members which contain data, and an ID. It is possible for the user to propagate an ID in all previous items located before an item located (that must itself be located before the current item) that he is asked to choose.
E.g. if we have A-B-C-D-E-F and the user is on E, he can choose to propagate a member ID of E to C and previous items (so A and B, too - but not to D).
Here's the code. Most or all the iterator loops are subject to unexpected behaviors.
void renameAllPreviousMembers(int memberIDToPropagate)
{
/* If first item, then there is no previous altogether */
if(m_currentItem != m_items.begin())
{
if(m_currentItem.m_members.contains(memberIDToPropagate) == false)
{
QMessageBox::critical(nullptr, tr("Rename members in previous items"),
tr("There is no member with the selected ID in the current item."),
QMessageBox::Ok);
return;
}
bool chose_item;
LinkedItems::iterator itemIter;
QStringList previousItems;
QStringList previousItemsBetterOrder;
QMap<QString, LinkedItems::iterator> previousItemsMap;
previousItemsMap.clear();
/* Find all previous items and store them in a QStringList and a map between those strings and actual items */
std::cout << "Search previous items : " << std::flush;
for(itemIter = m_items.begin();
itemIter != m_currentItem;
itemIter++)
{
/* Possibly add conditions on some items that we do not want to apply values to. */
std::cout << itemIter->name << ", " << std::flush;
previousItems << itemIter->name;
previousItemsMap.insert(itemIter->name, itemIter);
}
std::cout << "finished." << std::endl << std::flush;
QStringList::reverse_iterator riter;
for(riter = previousItems.rbegin(); riter != previousItems.rend(); riter++)
{
previousItemsBetterOrder << *riter;
}
QString name_previous_item = QInputDialog::getItem(nullptr,
QString("Previous item"),
QString("Previous item to start from : "),
previousItemsBetterOrder, 0, false, &chose_item);
if(!chose_item)
{
return;
}
/* Decode chosen previous item by retrieving it in the map */
LinkedItems::iterator chosenPrevIter = previousItemsMap.value(name_previous_item);
bool chose_member;
/* Find all existing IDs in previous item */
QStringList previousIDs;
QMap<int, Member>::const_iterator memberIter;
/* Retrieve all members from the chosen previous item */
std::cout << "Search IDs in chosen previous item : " << std::flush;
for(memberIter = chosenPrevIter->m_members.begin(); memberIter != chosenPrevIter->m_members.end(); memberIter++)
{
previousIDs << QString("%1").arg(QString::number(memberIter->ID));
std::cout << memberIter->memberID << ", " << std::flush;
}
std::cout << "finished." << std::endl << std::flush;
/* If no member then no lunch */
if(previousIDs.size() == 0)
{
QMessageBox::critical(nullptr, tr("Rename previous member"),
tr("There are no members in the selected previous item."),
QMessageBox::Ok, QMessageBox::Ok);
return;
std::cout << "Rename previous members finished with no members in chosen previous item." << std::endl << std::flush;
}
QString string_member_id_from_previous = QInputDialog::getItem(nullptr,
QString("Previous member number"),
QString("Member ID from previous item to be used : "),
previousIDs, 0, false, &chose_member);
if(chose_member)
{
int member_id_from_previous = string_member_id_from_previous.toInt();
/* Update member ID at and before chosen previous item */
std::cout << "Update member ID before chosen previous item : " << std::flush;
for(itemIter = chosenPrevIter;
itemIter != m_items.begin();
itemIter--)
{
std::cout << itemIter->name << std::flush;
if(itemIter->m_members.contains(member_id_from_previous))
{
/* Take and reinsert to new ID. */
std::cout << "+" << std::flush;
itemIter->m_members.insert(memberIDToPropagate, itemIter->m_members.take(member_id_from_previous));
}
else
{
/* Do nothing. */
}
std::cout << ", " << std::flush;
}
std::cout << "finished." << std::endl << std::flush;
}
}
else
{
/* Do nothing. */
}
}
else
{
QMessageBox::critical(nullptr, tr("Apply to previous members"),
tr("This is the first item. There are no previous items."),
QMessageBox::Ok);
}
std::cout << "Apply to previous members finished." << std::endl << std::flush;
}

QTextLayout::drawCursor() does not work when subclassing QAbstractTextDocumentLayout

currently QTextEdit and QPlainTextEdit does not meet my requirements, so I need to subclass QAbstractTextDocumentLayout to provide custom layout of the document. I reference QPlainTextDocumentLayout and QTextDocumentLayout a lot, and finally got a simple layout to display the text. However, I couldn't see the cursor in QTextEdit, which should be blinking. I need help to figure this out.
I am using Qt 5.9.1. The simple project is here. The draw() function of VTextDocumentLayout looks like this:
void VTextDocumentLayout::draw(QPainter *p_painter, const PaintContext &p_context)
{
qDebug() << "VTextDocumentLayout draw()" << p_context.clip << p_context.cursorPosition << p_context.selections.size();
// Find out the blocks.
int first, last;
blockRangeFromRect(p_context.clip, first, last);
if (first == -1) {
return;
}
QTextDocument *doc = document();
QPointF offset(m_margin, m_blocks[first].m_offset);
QTextBlock block = doc->findBlockByNumber(first);
QTextBlock lastBlock = doc->findBlockByNumber(last);
qreal maximumWidth = m_width;
while (block.isValid()) {
const BlockInfo &info = m_blocks[block.blockNumber()];
const QRectF &rect = info.m_rect;
QTextLayout *layout = block.layout();
if (!block.isVisible()) {
offset.ry() += rect.height();
if (block == lastBlock) {
break;
}
block = block.next();
continue;
}
QTextBlockFormat blockFormat = block.blockFormat();
QBrush bg = blockFormat.background();
if (bg != Qt::NoBrush) {
fillBackground(p_painter, rect, bg);
}
auto selections = formatRangeFromSelection(block, p_context.selections);
QPen oldPen = p_painter->pen();
p_painter->setPen(p_context.palette.color(QPalette::Text));
layout->draw(p_painter,
offset,
selections,
p_context.clip.isValid() ? p_context.clip : QRectF());
// Draw the cursor.
int blpos = block.position();
int bllen = block.length();
bool drawCursor = p_context.cursorPosition >= blpos
&& p_context.cursorPosition < blpos + bllen;
Q_ASSERT(p_context.cursorPosition >= -1);
if (drawCursor) {
int cpos = p_context.cursorPosition;
cpos -= blpos;
qDebug() << "draw cursor" << block.blockNumber() << blpos << bllen << p_context.cursorPosition << cpos;
layout->drawCursor(p_painter, offset, cpos);
}
p_painter->setPen(oldPen);
offset.ry() += rect.height();
if (block == lastBlock) {
break;
}
block = block.next();
}
}
I called layout->drawCursor() to draw the cursor but this function seems to do nothing.
Any help is appreciated! Thanks!
Update:
Add the log as following:
VTextDocumentLayout draw() QRectF(67,0 9x13) 19 0
block range 0 1
draw cursor 1 5 15 19 14
blockBoundingRect() 1 13 QRectF(0,0 75x17)
VTextDocumentLayout draw() QRectF(67,0 9x13) -1 0
block range 0 1
blockBoundingRect() 1 13 QRectF(0,0 75x17)
VTextDocumentLayout draw() QRectF(67,0 9x13) 19 0
When running this project in Linux, I coundn't see the cursor. However, when running it in Windows, I could see the cursor but not blinking.
Update:
It seems that QTextEdit pass a wrong clip rect to the layout. If I just inserted one line of text (only one block within the document), I could see the blinking cursor. Quite strange!!!
The default value of PaintContext class cursorPosition is -1.
http://doc.qt.io/qt-4.8/qabstracttextdocumentlayout-paintcontext.html#cursorPosition-var
If any default value is not provided to the cursor position ever (it is a public variable), then my guess is value remains -1, and drawCursor is always false (as the block position index minimum value is zero in worst case).
Try setting some default value to PaintContext::cursorPosition, which may display the cursor.
When subclassing QAbstractTextDocumentLayout, blockBoundingRect() should return the geometry of the block, not just the rect as what QPlainTextDocumentLayout does.
In one word, QPlainTextDocumentLayout provides a BAD example for subsclassing QAbstractTextDocumentLayout.

Doubleclick on QTreeView shall always return first columns value

I have my QTreeView where whole rows were selected:
ui->treeView->setSelectionBehavior (QAbstractItemView::SelectRows);
...and already filled with ID-Number <--> Description. All are structured as a tree. I can click it, and retreive corresponding selection via:
ui->lineEdit->setText( modelIndex.data(Qt::DisplayRole).toString() );
and I already connected:
connect(ui->treeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onSelectedTreeView(QModelIndex)));
When I click on a ID-Number, it works perfectly, and my modelIndex.data() returns the ID-Number.
When I click on the description, my modelIndex.data() returns its correct description (second column). BUT I would like to have again the corresponding ID-Number (first column).
I appreciate any help. Thanks in advance.
You can use the sibling(int row, int column) method of QModelIndex
In python:
def onSelectedTreeView(index):
firstColumnIndex=index.sibling(index.row(),0)
print(firstColumnIndex.data())
I guess you could do:
ui->lineEdit->setText(modelIndex.sibling(modelIndex.row(),0).data(Qt::DisplayRole).toString());
You can change treeView selectionBehavior to SelectItems. Than use index.row() and index.column().. And dont forget that it always start from 0
onSelectedTreeView()
{
//Put all your selected indexes into QModelIndexList
QModelIndexList _indexes = ui->treeView->selectionModel()->selectedIndexes();
// For each loop for every selected index..
foreach (QModelIndex index, _indexes)
{
//if your index data == 0 it means that you clicked into ID-number field.
//So you can easly see which index you clicked with qdebug
qDebug() << "Row = " << index.row() << "\t Column = " << index.column();
// So if you want to get always index of first column(ID-Number) use QAbstractItemModel
if(index.column().toInt() != 0 ) //if your index not equal to 0
{
const QAbstractItemModel* absModel = index.model();
// change index of absModel from index.column() to 0;
QModelIndex changedIndex = absModel->data(absModel->index(index.row(), 0), Qt::DisplayRole).toInt();
qDebug() << "Row = " << changedIndex.row() << "\t Column = " << changedIndex.column();
}
}
}

QTreeView::dropMimeData - setting values for a new child

I am trying to drop some mime encoded text onto a tree view. It is working - the dropMimeData() method is called, I can decode the mime data into the strings that were dropped, I can insert a child into the model which shows up in the view, but ... I can't find a way to set the text value of the new item/row to the string dragged and dropped (or any string for that matter).
Here is some of the code i've tried inside the dropMimeData() method:
if ( ( row == -1) && (column == -1) && parent.isValid() ) {
int mdlidx = this->data(parent, Qt::DisplayRole).ModelIndex;
qDebug() << "mdlidx: " << mdlidx;
// treet text - the text of the cell that gets dropped onto
QString tt = this->data(parent, Qt::DisplayRole).toString();
qDebug() << "tree text: " << tt;
TreeItem *item = this->getItem(parent);
int ccnt = item->childCount();
qDebug() << "ccnt: " << ccnt ;
if ( item->insertChildren(0, 1, 0) ) {
qDebug() << "Child Inserted";
// how do I access the new child item here ???
} else {
qDebug() << "Failed";
}
How do I access the new child item in order to set the text that would be visible in the view?
I'm using the QStandardItemModel, if that makes any difference.
My solution to this is to create a signal and slot - I emot the signal in the dropMimeData() method and the slot is in a part of the code that has the view and model, so can easily update the model.
I send the mime data and the parent across using the signal.
I'm not sure if this is the correct way of doing this, but it is working.

Error on adding to empty comboBox in Qt

I use Qt 5.2.0 (MSVC 2010).
I added to my form in Qt a ComboBox.
Then I want to fill it with numbers:
for (i = 0; i < n; i++){
ui->tableCombo->addItem(QString::number(i));
}
When I add a first element right in the form, it successfully adds numbers. But when I leave it empty, it throws an error:
ASSERT failure in QVector::operator[]: "index out of range"
Debugger shows that error occured right in this line. And there is no QVector across the line.
After adding qDebug().
qDebug() << "readFileToStringList: msg10";
for (i = 0; i < n; i++){
qDebug() << "readFileToStringList: msg20 i = " << i;
ui->tableCombo->addItem(QString::number(i+1));
qDebug() << "readFileToStringList: msg30";
}
qDebug() << "readFileToStringList: msg40";
I get the same result
readFileToStringList: msg10
readFileToStringList: msg20 i = 0
ASSERT failure in QVector<T>::operator[]: "index out of range", file C:\Qt\Qt5.2.0\5.2.0\mingw48_32\include/QtCore/qvector.h, line 369
I had this exact problem and couldn't figure it out for a couple hours. I realized ::addItem() was triggering the indexChanged(int) signal, which I had connected to a function that was causing an out-of-range error in a container.
I would say it was possibly the problem here too, but I'm sure the OP has moved on since then. To me it isn't exactly intuitive that the indexChanged signal would be called on insertion of new items, since it doesn't actually change the currentIndex.
Hopefully if anyone else gets tripped up this will help them!
addItem() doesn't throw that error! I'm positive it's coming from another instruction in your code.
Qt documentation has an entire section on Debugging Techniques, but if you are afraid of debuggers you can use the poor's man debugger: spread several qDebug() messages before and after the instructions you think are responsible for the problem:
qDebug() << "methodX: msg10";
for (i = 0; i < n; i++){
qDebug() << "methodX: msg20 i = " << i;
ui->tableCombo->addItem(QString::number(i));
qDebug() << "methodX: msg30";
}
qDebug() << "methodX: msg40";
If the message methodX: msg30 gets printed to the screen, means that addItem() didn't cause the error.

Resources