PDA

View Full Version : QListWidget in a tab not scrolling on setCurrentRow()



maird
22nd July 2007, 01:57
Using Qt 4.2.1/Linux/Eclipse Europa with CDT and Qt plugins.

My application needs a font selection dialog box that restricts available fonts to fixed pitch, non-symbol fonts. The application only displays with one font but you can change which. So, the font selection dialog has a QListWidget of suitable fonts with the application's current font already selected in the list.

My first cut was a Designer generated QDialog with a QListWidget and other necessary controls. I populated the list by filtering a QFontDatabase in my QDialog constructor. I added a member to set the selected font. Iterated the list for the one with the same family name and:



ui.fontList->setCurrentRow(row);
ui.fontList->item(row)->setSelected(true);


That all works nicely, the item is selected and is visible (scrolled into view) in the QListWidget. I suspect there's a redundancy there though but it's harmless at this point.

I decided to move the font selection from a dedicated dialog to a tab on another configuration dialog. I copied the code and repeated the Designer layout and very carefully verified everything was copied and fixed up properly. This time, the correct item is selected but, it is not scrolled into view unless it was in the top visible items. Just for kicks I added a scrollToBottom() after the above and it had no effect. Remember the correct item is selected so I know execution is passing through this but I single stepped it and confirmed it anyway.

As far as I can tell the only difference is that the QListWidget's parent has gone from being a QDialog to being a QWidget in a QTabWidget.

Thanks for any advice/suggestions.

marcel
22nd July 2007, 07:34
You can try this after you set the current row:


QModelIndex index = ui.fontList->currentIndex();
ui->fontList->scrollTo(index, QAbstractItemView::EnsureVisible);


Also, you can use QListWidget::scrollToItem:


QListWidgetItem item = ui.fontList->item(row);
ui->fontList->scrollToItem(item, QAbstractItemView::EnsureVisible);


Regards

maird
22nd July 2007, 16:50
Marcel: Many thanks for the suggestions but neither worked. I had tried scrollToItem(). I didn't mention it because scrollToBottom() showed the same behaviour - sorry.

I'm going to try to reproduce this with a simple example in a clean project. In the meantime, I'd be grateful for any other suggestions.

FWIW, there were a couple of errors in the code you posted. I'll correct them for anyone reading later. In line 2 of the first example ui is not a pointer and the operator to access fontList should be . rather than ->



QModelIndex index = ui.fontList->currentIndex();
ui.fontList->scrollTo(index, QAbstractItemView::EnsureVisible);


In the second one QListWidget::item() returns a pointer to a QListWidgetItem



QListWidgetItem* item = ui.fontList->item(row);
ui.fontList->scrollToItem(item, QAbstractItemView::EnsureVisible);

maird
22nd July 2007, 18:08
I cannot reproduce it with a new, clean project and the simplest implementation I can come up with and using the following to select the item:



ui.listWidget->setCurrentRow(row);
ui.listWidget->item(row)->setSelected(true);


Therefore I assume the problem case is a side-effect of something else in my real project. I'll use the test project that works as a reference and fix it in the real project.

Marcel: many thanks for the suggestions.

maird
22nd July 2007, 19:24
Whoa there. Scratch my previous comment. I can reproduce it with a simple sample.

Here's how I did it:


Create a main window project
Add a menu (File)
Add Test and Exit items to the File menu
Connect File->Exit to the main window class close()
Create a dialog with an associated class
Add a tab control to the dialog
Add a QListWidget to the tab
Modify the constructor of the dialog class to populate the list
Add a member function to the class that takes a QString, searches for it in the QListWidget and setCurrentItem() then setSelected(true)
Create a slot for File->Test in the main window class
Make the member create an instance of the dialog class
Call the dialog class member that selects an item, passing an item QString that would not normally be visible when the dialog shows
exec() the dialog
Build and run - Use File->Test, the item is selected and is visible
Shutdown program
Change the layout of the tab widget in the dialog to Grid
Build and run - The item is selected but is not visible


If the tab layout had no layout the problem is not observed! That makes no sense as a cause but it happens as soon as I set the layout of the tab widget to grid. I didn't try any other layouts. Bug?

NB: Using Qt 4.2.1 on x86_64 Linux, Eclipse + CDT + Qt for project and build.

If someone can try to reproduce it with a later Qt version using the instructions above I'd appreciate the data.

maird
22nd July 2007, 19:25
BTW, if the QListWidget is a child of a QDialog that has a grid layout the problem is not observed. IOW, it's only in a grid layout tab widget (so far).

marcel
22nd July 2007, 19:27
So many instructions :)...
Why don't you posted the code instead?
Would have helped...

Regards

marcel
22nd July 2007, 19:29
...IOW, it's only in a grid layout tab widget (so far).
What about the layout of the dialog in which the tab widget is.
What layout did you set for it?

Regards

maird
22nd July 2007, 19:32
I did some other testing. If I leave the tab with no layout and add a QGridLayout to the tab then put the QListWidget in that QGridLayout it works OK. If I then give the QTabWidget grid layout (remember it still contains a QGridLayout containing a QListWidget) then the problem appears again. Bizarre!

maird
22nd July 2007, 19:35
What about the layout of the dialog in which the tab widget is.
What layout did you set for it?

It happens when the containing dialog has no layout and when it has grid layout.

I'll post the code, I'm new here. Is it OK to post the whole sample project, e.g. as a tarball attachment?

marcel
22nd July 2007, 19:37
I'll post the code, I'm new here. Is it OK to post the whole sample project, e.g. as a tarball attachment?
Sure. Even better.

Regards

maird
22nd July 2007, 19:58
Attached listWidgetTest.tar.bz2 containing a clean sample project that reproduces the problem.

It's a main window app with a Test menu.


Test->Exit terminates the program
Test->Dialog shows a QDialog selecting an item and scrolling to it
Test->Tabs Bad shows a dialog with a tab control with a grid layout selecting an item but not scrolling to it
Test->Tabs Good shows a dialog with a tab control that has no layout selecting an item and scrolling to it


There's a QDialog derived class for all of the dialog forms and they all use the same implementation to populate the list and select an item. Thanks for your time Marcel.

marcel
22nd July 2007, 20:51
Yes, I can confirm the behavior.
If you put the list in a tab, and set to the tab widget any kind of layout, then auto scrolling will not work.

Debugging scrollToItem shows that the list widget actually thinks that the target item is already visible and it just updates the item rect. It does not adjust the scrollbars, as it should.

Perhaps the scrollbars should be adjusted manually?

I think this requires a little more research.

Regards

maird
22nd July 2007, 22:39
Marcel: Thanks for the confirmation. I had tried getting the QListWidget's vertical scrollbar and setting the position on it. That had no effect either but I think I'll try it again in this simple project.

maird
23rd July 2007, 01:08
I tried using the scrollbar directly and that didn't work either. I added the following after selecting the found item:



QScrollBar* sb = ui.listWidget->verticalScrollBar();
int min = sb->minimum();
int max = sb->maximum();
int range = max - min;

// Take the found row as a fraction of the count
int r = row + 1;
int c = ui.listWidget->count();
int v = (100 * r) / c;

// Move to that fraction of the scrollbar range
int p = (range * v) / 100;
p += min;
sb->setValue(p);

maird
23rd July 2007, 02:43
I single stepped the scrollbar code above and was surprised to find the min and max values for the scrollbar are zero. The code above (and anything that assumes it can scroll with QScrollBar::setValue()) will not scroll at all. That's probably why "the list widget thinks the item is already visible" in your debugging.

But the scrollbar visually seen on the dialog has a non-zero range and works to scroll the list. So, I added the following between lines 1 and 2:



sb->setRange(0, ui.listWidget->count() - 1);


Then the sb->setValue(p); works to show the selected item. So, I figured just use setRange() in the constructor after populating the list and the normal setCurrentItem()/setSelected(true) will work as expected. Nope, the scrollbar range is 0, 0 by the time you get to the function that selects an item.

I suppose I can workaround it by setting the scrollbar range before trying to find and select anything in it. I think there's a Qt bug here.

maird
23rd July 2007, 03:10
I instrumented the loop that finds the item to be selected and looked at the scrollbar range after every operation. It is 0, 20 at the start and stays that way until:



ui.listWidget->setCurrentRow(row)


Across that function call the scrollbar range changes to 0, 0. Setting the scrollbar range anywhere before calling setCurrentRow() isn't a workaround. So, I save the scrollbar range on entry to the function that selects an item and set the range to the saved values after using setCurrentRow(). I thought it would be a simple matter of using QListWidget::scrollToItem() to scroll the item into view but that doesn't work. I'll need to use QScrollBar::setValue().

Cool bug!

maird
23rd July 2007, 03:40
This is my workaround:



void listTab::changeTo(QString& it)
{
bool funkysb;
int min;
int max;
int range;
int row;
int r;
int c = ui.listWidget->count();
int v;
QString number;
QScrollBar* sb;

sb = ui.listWidget->verticalScrollBar();
min = sb->minimum();
max = sb->maximum();
range = max - min;

funkysb = ((range == 0) && (c != 0));
if (funkysb)
{
// The scrollbar range is zero and the control contains
// items, set the scroll range to something useful
min = 0;
max = c - 1;
range = max - min;
sb->setRange(min, max);
}

// Find the specified number in the list
for (row = 0; row < c; row++)
{
number = ui.listWidget->item(row)->text();

if (number == it)
{
// Make it the current selection
// This resets the scrollbar range to 0,0 if the
// list is in a tab widget with a layout
ui.listWidget->setCurrentRow(row);
ui.listWidget->item(row)->setSelected(true);

// Reset the scrollbar range if we have funkiness
r = sb->maximum() - sb->minimum();
if (funkysb && (r != range))
{
sb->setRange(min, max);

// Scroll to the item
sb = ui.listWidget->verticalScrollBar();
r = (row + 1) * range;
c = ui.listWidget->count();
v = r / c;
if (r % c)
{
v++;
}
sb->setValue(v);
}
break;
}
}
}

maird
23rd July 2007, 05:58
This never ends. The workaround works (it can be made simpler) but it only works if the QListWidget has whatever default size designer would give it in a grid control with a layout and no other controls! As soon as you change it from the default size either by re-sizing the tab control in designer or by adding controls to the tab driving the scrollbar stops working. The scrollbar works but it doesn't scroll as far as you expect so that the last few items can't be scrolled to. The number you can't reach depends on how far the control is from this "preferred" size.