PDA

View Full Version : QUiLoader flawed/not providing name for Layouts. Workaround?



ZB
30th September 2006, 23:49
I have an interface built with the Designer in which I have a layout that contains some widgets. I need to programatically get a pointer to this layout so that I can insert a QScrollArea, because it is not available (forgotten?) in the Designer.

When using the UIC tool I get both a name (even though the Designer does not write one in the .ui-output) and the convenient pointer needed, however I have switched to using the QUiLoader and this is where I run into problems, because it does not function in the same way the UIC tool does as you would expect; it will not provide pointer nor name.

Calling findChild( ) with the name that the UIC otherwise will construct gives a null pointer, as does calling layout() on one of the widgets placed inside my layout (I was told this is a normal behaviour though I can not understand why). How do I work around this?

Thanks

wysota
30th September 2006, 23:55
I need to programatically get a pointer to this layout so that I can insert a QScrollArea, because it is not available (forgotten?) in the Designer.
It's not forgotten. It's not there for a reason - QScrollArea is rarely used without subclassing. If you really need QScrollArea, make a simple plugin for Designer to make it available.


Calling findChild( ) with the name that the UIC otherwise will construct gives a null pointer, as does calling layout() on one of the widgets placed inside my layout (I was told this is a normal behaviour though I can not understand why). How do I work around this?

layout() returns pointer to a layout which manages children of a given widget, so you need to call layout() on the parent widget instead.

ZB
1st October 2006, 00:19
Thanks, when doing this I get a pointer back rather than 0, however the application still crashes when trying to add my QScrollArea to the layout. Any hints?

EDIT: First time I tried layout() on the central widget I got a valid pointer, but consecutive runs yield a 0 pointer like previously :S

Main

int main(int arg_count, char **arg_list)
{
QApplication juliet(arg_count, arg_list);
QApplication::setStyle(new QPlastiqueStyle);

mainwindow ui;
ui.show();

return juliet.exec();
}



Class header

class mainwindow:public QMainWindow
{
Q_OBJECT

public:

mainwindow();

QScrollArea *render_window;
QLabel *label_render;
QGridLayout *render_layout;

...

};



Class implementation

mainwindow::mainwindow()
{
QUiLoader loader;
QFile file(":/interface/juliet.ui");
file.open(QFile::ReadOnly);
QWidget *central = loader.load(&file);
setCentralWidget(central);
file.close();

label_render = findChild<QLabel*>("label_render");
render_layout = qobject_cast<QGridLayout*>(central->layout());
render_window = new QScrollArea;

QPixmap pixmap(200, 200);
pixmap.fill(Qt::black);
label_render->setPixmap(pixmap);
label_render->setAlignment(Qt::AlignCenter);
render_window->setWidget(label_render);
render_window->setWidgetResizable(true);
render_window->setFocusPolicy(Qt::NoFocus);
render_window->installEventFilter(this);
render_layout->addWidget(render_window, 0, 0, 1, 4); // <-- Application crash

QMetaObject::connectSlotsByName(this);
}

wysota
1st October 2006, 00:47
render_layout = qobject_cast<QGridLayout*>(central->layout());
Does this return a valid (!=0) pointer?

Did you try running the code under debugger and checking the backtrace?

ZB
1st October 2006, 00:54
Very sorry, I was not being clear; that pointer is indeed a null pointer (except in a single odd run where it was non-zero) and I am clearly aware this is the reason my code crashes since obviously I can not reference data outside bounds.

The code was just an excerpt to shed some light of what I am doing, my real question is how to get around this problem QUiLoader introduces - how to obtain the layout-pointer. If there is no easy way then I will simply go back to using UIC output again.

Thanks

wysota
1st October 2006, 10:02
Very sorry, I was not being clear; that pointer is indeed a null pointer (except in a single odd run where it was non-zero) and I am clearly aware this is the reason my code crashes since obviously I can not reference data outside bounds.


Maybe the layout is not a grid layout but some other kind of layout?

ZB
1st October 2006, 11:45
Thanks for your help, Wysota. I have been working some more with this, and it seems the only way I can get hold of this QGridLayout in my case is with some dirty code:


QList<QGridLayout*> grids = findChildren<QGridLayout*>();
render_layout = grids[1]; // 0 = central widget layout, 1 = outer most layout which i need


Some observations:


It was not possible to retrieve the layout by any means of calling parent() or layout() on actual or parental widgets, Though there may be some workaround I have not found.
Calling findChild<QGridLayout*> with the standard name constructions for layouts in some cases yielded non-zero pointers, but they were all wild and caused program crashes. This appears to be a bug, but there might be some explanation to it.
GUiLoader does not provide the same functionality as the UIC tool as one would expect; the UIC will give layouts a standard name construction, the GUiLoader will not.
The Qt Designer does not expose QObject::objectName for layouts. It will set a name when dropping a layout widget, but not when dropping it on a group of selected widgets. Regardless, it will ignore objectName property when saving the form.

Conclusion:


Solution: Qt Designer must expose objectName for layouts so users can programatically obtain and use the layout in a safe way. The Designer must also be changed so it outputs this property when saving forms.


I will fiddle with this a bit more and probably send a bug-report/feature request.

wysota
1st October 2006, 12:05
Solution: Qt Designer must expose objectName for layouts so users can programatically obtain and use the layout in a safe way. The Designer must also be changed so it outputs this property when saving forms.
Are you sure this is necessary? The layout can always be retrieved using QWidget::layout() on a proper widget, so it is enough to have the name of the widget itself and then fetch its layout. If a layout is working, it has to be set as a widget's layout or a sublayout of this layout. Layouts can't be standalone - that's why an additional widget is created by Designer for "floating" layouts, so that they can be accessed through the widget interface.

ZB
1st October 2006, 12:49
Maybe you can solve this for me once and for all, and we can conclude if it is lack of knowledge or a syntactical error on my part, or a bug/flaw in either the QUiLoader or the Designer :-)

My form is a QMainWindow, placed top-most in it I have a QGridLayout containing a few widgets, and I need a pointer to this grid layout. The following things are not working:

(The form and other code is correct, because it will create, display and function properly when I obtain the pointer using previously pasted code)


pointer = qobject_cast<QGridLayout*>( (qobject_cast<QLabel*>( topmost_label_inside_layout->parent() ))->layout() );
pointer = qobject_cast<QGridLayout*>( central_widget->layout() );
pointer = qobject_cast<QGridLayout*>( main_window->layout() ); // long shot



Putting my grid layout inside an additional dummy widget to solve this is not an acceptable overhead, sorry to say :S

wysota
1st October 2006, 15:20
What do you mean by that the grid layout is "placed top most in it"? Did you do that in Designer? If, then how? You should apply the layout to the widget itself, not "place it in the widget". I mean you shouldn't have a red rectangle visible inside the central widget. Could you attach the ui file (if you have one) here so that we can check it? If my guesses are correct, then you have placed an additional widget in the window yourself and that's precisely why it is not working... I may be wrong though, so I'd like to see that ui file (or the code you used to generate that hierarchy, if you're not using Designer there).

ZB
1st October 2006, 16:56
I drew widgets right inside the central widget, selected some of them and applied a grid layout on them, and then applied a grid layout to the entire window itself. Is this wrong? This is how it was done in the demo video on trolltech.com, and sure enough the interface behaves perfectly.

I have not seen any mentioning that you should place dummy widgets around every single layout that shares hierarchy level with other widgets. It seems superfluous and as far as I can see the only benefit would be that you can specify a unique name for every dummy widget so that you programatically can find the layout manager inside it, which in itself is alot of extra work aswell as contradictory to how some parts of Qt work. Simplest and most convenient way would still be to expose objectName in layouts so you can acquire the layout safely and with minimal amount of work.

Simplified version of the interface file is attached.

wysota
1st October 2006, 17:08
and sure enough the interface behaves perfectly.
It doesn't as you can't access the layout ;)


I have not seen any mentioning that you should place dummy widgets around every single layout that shares hierarchy level with other widgets.
I didn't say you should. Designer places floating layouts into QWidgets to be able to save the file. There is a thread on this forum which mentions that.


Simplified version of the interface file is attached.
So which layout you want to access?

wysota
1st October 2006, 17:23
I guess this might be what you wanted. Seems to work fine for me...


#include <QApplication>
#include <QUiLoader>
#include <QFile>
#include <QLabel>
#include <QLayout>
#include <QGridLayout>

int main(int argc, char **argv){
QApplication app(argc, argv);
QUiLoader loader;
QFile file("temp.ui");
file.open(QFile::ReadOnly);
QWidget *widget = loader.load(&file, 0);
if(!widget) return 2;
QWidget *l = widget->findChild<QWidget*>("centralwidget");
if(!l){
qDebug("widget not found");
delete widget;
return 2;
}
QLayout *lay = l->layout();
if(!lay){ qDebug("widget has no layout"); return 2; }
QGridLayout *grid = qobject_cast<QGridLayout*>(lay);
if(!grid){ qDebug("Not a grid"); return 2; }
grid->addWidget(new QLabel("TESTING"), 0, 0, 1, 4);
widget->show();
int r = app.exec();
delete widget;
return r;
}

ZB
1st October 2006, 18:02
Thanks alot for taking your time to write this code, I see now it is doable just like you said, as long as you have that central widget to start from. My two lines of dirty code is convenient even if they are not very appreciating of changes in the the UI-file, and in any case I hope you agree that the possibility to replace all of this with findChild("my_layout") is attractive, even more so considering that you would not need dummy widgets as "search words" when having multiple layouts in the same hierarchy level.

The layout object inherits QObject and both the Designer and UIC tool set and provide names for layouts in certain cases - Qt just need to go all the way with this. I will ask Trolltech for this feature and hope they agree with me :-)

Thanks alot again for your help, Wysota.