PDA

View Full Version : Delayed Rendering of QTabWidget Tabs



mclark
7th May 2007, 22:02
Greetings,

I have a dialog containing 4 tabs. Each tab displays a form created by Qt Designer. In the constructor of each widget that is drawn on a tab, I call
ui.setupUi( this ) which appears to draw the form on the tab widget. I would like to delay the rendering of the non-visible forms until their tab has been selected. Does anyone know if this is possible and if so, how?

I have tried delaying the call to ui.setupUi() until its tab is selected, but then the form is not drawn. Only if ui.setupUi() is called in the constructor is the form visible when the tab is selected.

Background:
My application connects to hardware on the network and must ACK each device every 600ms to keep the device connected. When there are a large number of devices online (~250) the cost of drawing all the complicated forms in the dialog starves the thread that is doing the ACK and the devices must reconnect. My thinking is that if I can delay rendering the forms that are not visible I can keep the devices active without impacting performance of the UI.

wysota
7th May 2007, 22:24
setupUi doesn't do any drawing. It only creates parent-child relationship between widgets. Your widgets will be drawn for the first time when their tab index is being activated. When they are not visible, they will not be drawn at all.

If you delay setupUi(), you have to manualy call show() on the widget.

If you experience slowdowns, they are probably not due to expensive rendering.

mclark
7th May 2007, 22:54
I'm not sure how show() is to be called when delaying the call to setupUi().

When a tab is selected I tried:

if ( !m_tabACN_Ports->m_bIsInitialized )
{
m_tabACN_Ports->InitializeTabUI();
m_tabACN_Ports->show();
}


void ACNPortsTab::InitializeTabUI( void )
{
ui.setupUi( this );

// Set field values from online ACN Gateway map
ui.userNameLabel->setText( m_parent->m_sName );
ui.ipLbl->setText( m_pNode->GetIPAddr() );

. . .
The ACNPortsTab is defined as:
class ACNPortsTab : public QWidget

I still get nothing on the tab unless I call setupUi() in the constructor.

The slowdown we're seeing is seen when the dialog is started. After all devices have been connected, the processor is idling (just sending ACKs to the devices). When the edit dialog is started the processor spikes and devices start dropping. This is why I'm trying everything to separate the parts of the dialog startup.

wysota
8th May 2007, 02:22
I'm not sure how show() is to be called when delaying the call to setupUi().


void ACNPortsTab::InitializeTabUI( void )
{
ui.setupUi( this );

// Set field values from online ACN Gateway map
ui.userNameLabel->setText( m_parent->m_sName );
ui.ipLbl->setText( m_pNode->GetIPAddr() );
show(); // <-- here




The slowdown we're seeing is seen when the dialog is started. After all devices have been connected, the processor is idling (just sending ACKs to the devices). When the edit dialog is started the processor spikes and devices start dropping. This is why I'm trying everything to separate the parts of the dialog startup.

I'm still convinced paint events are not delivered to hidden widgets. If you don't believe me, install an event filter on your child tabs and scan for QEvent::Paint and QEvent::Show. Your slowdown is probably caused by the constructor or incorrect handling of the connection between the widget and the device behind it. I suggest using a profiler like gprof or cachegrind (or other, depending on the platform you use) to find the bottleneck instead of shooting blind.

mclark
8th May 2007, 16:22
wysota, thanks for taking time to think about this.. You are correct that I need to run this code through a profiler. I'm currently looking for an appropriate one while our IT department drags its heels doing the same thing :rolleyes:

While I suspected that the rendering was done on an as needed basis, as you suggest, I was instructed to try to separate the initialization from the rendering. I'll just wait until the profiler issue is resolved and this will show where the bottleneck actually is.

Although I suspect there are other forces at work here (not just the drawing) the show() suggestion doesn't work for me.

I tried a separate call to show() after initialization call (the one that calls ui.setupUi()); the tab was blank. I then moved the show() to the end of the initialization function with the same result, blank tab. Since profiling is what's really needed here I'll just work on some other 'highest priority' problem ;) .

-m-

marcel
8th May 2007, 19:31
Why don't you start with all the forms hidden, and show only the one in the active tab?
Wysota is right - if a widget is hidden it will receive no update/resize events.

Whenever you select another tab you show the current form and hide the others.

And another thing: Do you update the contents of any widgets( list views, tables, etc ) in the forms somehow? From another thread or from the corresponding device(s) maybe?.

Because even if they are hidden or not their contents will be updated. This could have an impact on performance too.

How many widgets does one of those forms contains anyway? Are there many layouts involved( relayouting could take a long time on complex configurations )?

Regards

wysota
8th May 2007, 20:36
If you could show us the constructor and paint event (if overriden) of the widgets in question, maybe we could help you solve the issue without waiting for your IT departament.

mclark
9th May 2007, 01:37
Thank you for trying to help a Qt novice with this problem. I've learned a lot from this forum already.

I don't override the paint event, although we ARE using the Arthur style.

The classes are defined as such:

class EditTabWidget : public QTabWidget
{
public:
EditTabWidget( QWidget *pParent = 0 ) : QTabWidget( pParent )
{
// Only change the background color of the selected tab
QPalette pal = tabBar()->palette();
pal.setColor( QPalette::Base, COLOR_ManageBkg );
tabBar()->setPalette( pal );
}
}; // class EditTabWidget : public QTabWidget


class ACNGenTab : public QWidget
{
Q_OBJECT

public:
ACNGenTab( QWidget* pParent = 0 );
void ApplyUpdates( void );
void accept( void );
void reject( void );

private slots:
void on_nameEdt_editingFinished( void );
void on_ipAddrEdt_textChanged( const QString& sIP );
void on_subnetMaskEdt_textChanged( const QString& sIP );
void on_defIPEdt_textChanged( const QString& sIP );
void on_tftpSrvrIPEdt_textChanged( const QString& sIP );
void on_dynamicRadBtn_toggled( bool bState );
void on_staticRadBtn_toggled( bool bState );
void on_editIPBtn_clicked( void );
void on_resetBtn_clicked( void );
void on_identifyBtn_clicked( void );
void on_dwnldBtn_clicked( void );
void on_rebootBtn_clicked( void );
void on_clearBtn_clicked( void );

protected:
bool validateIPAddress( const QString& sIP );

public:
Ui::ACNCfgGenDlg ui;

private:
EditACNDlg * m_parent;
CController * m_pController;
CACNGateway * m_pNode;

}; // class ACNGenTab : public QWidget


///////////////////////////////////////////////////////////////////////////////
// ACN Port Properties Tab Class

class ACNPortsTab : public QWidget
{
Q_OBJECT

public:
ACNPortsTab( QWidget* pParent = 0 );
void InitializeTabUI( void );
void ApplyUpdates( void );
void showPriorityDialog( int nPort );
void showAIPDialog( int nPort );
void accept( void );
void reject( void );

protected:
void ApplyPort1Updates( void );
void ApplyPort2Updates( void );
void ApplyPort3Updates( void );
void ApplyPort4Updates( void );

private slots:
// ... lots of slots to record and/or validate UI data changes

public:
Ui::ACNCfgPortsDlg ui;

bool m_bIsInitialized;

private:
EditACNDlg * m_parent;
CController * m_pController;
CACNGateway * m_pNode;

}; // class ACNPortsTab : public QWidget


///////////////////////////////////////////////////////////////////////////////
// ACN AIP Properties Tab Class

class ACNAIPTab : public QWidget
{
Q_OBJECT

public:
ACNAIPTab( CACNGateway* pNode, MainWin* pMainWin, QWidget* pParent = 0, int nPort = -1 );

void InitializeTabUI( void );
int GetCurrentPort( void );
QString GetUniverseNum( void );

QTableWidget* GetCurrentTable( void ) { return m_pCurrTable; }

void ApplyUpdates( void );
void accept( void );
void reject( void );

private slots:
void on_copyBtn_clicked();
void on_oneToOneBtn_clicked( void );
void on_rangeBtn_clicked( void );
void on_clearBtn_clicked( void );
void portCboIdxChange( int nIndex );
void on_aipChk_stateChanged( int nState );

void updateFreeChannelNum( void );

public:
Ui::ACNAIPDlg ui;

MainWin* m_pMainWin;
bool m_bIsInitialized;

private:
EditACNDlg * m_parent;
CController * m_pController;
CACNGateway * m_pNode;

bool m_bOnline;
int m_nPort;
QTableWidget* m_pCurrTable;
bool m_bPort1AIPEnabled;
bool m_bPort2AIPEnabled;
bool m_bPort3AIPEnabled;
bool m_bPort4AIPEnabled;

}; // class ACNAIPTab : public QWidget


///////////////////////////////////////////////////////////////////////////////
// ACN About Properties Tab Class

class ACNAboutTab : public QWidget
{
Q_OBJECT

public:
ACNAboutTab( QWidget* pParent = 0 );
void InitializeTabUI( void );
void accept( void ) {}
void reject( void ) {}

public:
Ui::ACNCfgRODlg ui;

bool m_bIsInitialized;

private:
EditACNDlg* m_parent;

}; // class ACNAboutTab : public QWidget


///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// Edit ACN Properties Class

class EditACNDlg : public QDialog
{
Q_OBJECT

public:
EditACNDlg( MainWin* pMainWin, const int type, void* pData, bool bIsCfgTable,
bool bAllowIPEdit, bool bOnlineUpdate = true, QWidget* pParent = 0 );
virtual ~EditACNDlg();

// Return current values for Name, IPAddress and boundNode.
void GetCellUpdates( QString& sName, QString& sIP,
uint1& nP1Mode, uint1& nP2Mode, uint1& nP3Mode, uint1& nP4Mode,
uint4& nP1Univ, uint4& nP2Univ, uint4& nP3Univ, uint4& nP4Univ );

private slots:
void on_okPushButton_clicked() { accept(); }
void on_cancelPushButton_clicked() { reject(); }
void setFocusInTab( int nTabIndex );

public:
MainWin * m_pMainWin;
CController * m_pController;
CACNGateway * m_pNode;
bool m_bAllowIPEdit;

int m_devType;
QString m_sName;
QString m_sCID;
QString m_sIPAddr;
bool m_bIsDynamicIP;
QString m_sSubnetMask;
QString m_sTFTPIP;

// ...lots more data members

bool m_bOnlineUpdate;
bool m_bIsCfgTable;
bool m_bInitializing;

private:
void accept( void );
void reject( void );

Ui::EditPropsDlg ui;

EditTabWidget* m_tabWidget;
ACNGenTab* m_tabACN_Gen;
ACNPortsTab* m_tabACN_Ports;
ACNAIPTab* m_tabACN_AIP;
ACNAboutTab* m_tabACN_About;

}; // class EditACNDlg : public QDialog

This is the constructor of the dialog


EditACNDlg::EditACNDlg( MainWin* pMainWin, const int type, void* pData,
bool bIsCfgTable, bool bAllowIPEdit,
bool bOnlineUpdate, QWidget* pParent ) : QDialog( pParent )
{
// Initialize class member variables
m_pMainWin = pMainWin;
m_pController = m_pMainWin->m_pController;
m_devType = type;
m_bOnlineUpdate = bOnlineUpdate;
m_bAllowIPEdit = bAllowIPEdit;
m_bIsCfgTable = bIsCfgTable;
m_bInitializing = true;

m_pNode = reinterpret_cast<CACNGateway*>( pData );
if ( m_pNode == NULL )
return; // ERROR: device not found in online map

// Setup the dialog UI
ui.setupUi( this );
setFixedSize( 579, 641 );

// Setup the QTabWidget
m_tabWidget = new EditTabWidget( this );
m_tabWidget->setTabShape( QTabWidget::Triangular );
m_tabWidget->setFixedSize( 560, 565 );

QPalette palette;
palette.setColor( m_tabWidget->backgroundRole(), Qt::white );
m_tabWidget->setPalette( palette );

connect( m_tabWidget, SIGNAL( currentChanged( int ) ), this, SLOT( setFocusInTab( int ) ) );

// Setup the individual tab-specific widgets
m_tabACN_Gen = new ACNGenTab( this );
m_tabWidget->addTab( m_tabACN_Gen, tr(GENERAL_TAB.toAscii().data()) );

m_tabACN_Ports = new ACNPortsTab( this );
m_tabWidget->addTab( m_tabACN_Ports, tr(PORTS_TAB.toAscii().data()) );

m_tabACN_AIP = new ACNAIPTab( m_pNode, m_pMainWin, this );
m_tabWidget->addTab( m_tabACN_AIP, tr("AIP") );

if ( !m_bIsCfgTable )
{
m_tabACN_About = new ACNAboutTab( this );
m_tabWidget->addTab( m_tabACN_About, tr(ABOUT_TAB.toAscii().data()) );
}

// Layout the Dialog elements
QVBoxLayout* mainLayout = new QVBoxLayout;
mainLayout->addWidget( m_tabWidget );
mainLayout->addSpacing( 70 ); // Force tabs to top of dialog
setLayout( mainLayout );
setWindowTitle( tr("DMX Gateway Configuration") );

// Set focus on 1st editable element on the first tab
m_tabACN_Gen->ui.nameEdt->setFocus();
m_tabACN_Gen->ui.nameEdt->setCursorPosition( 0 );

// Set the "What's This?" text
m_tabWidget->setWhatsThis( WT_ACN_TAB );
ui.okPushButton->setWhatsThis( WT_OK_BTN );
ui.cancelPushButton->setWhatsThis( WT_CANCEL_BTN );

m_bInitializing = false;

} // EditACNDlg::EditACNDlg(

This is the constructor of one of the pages being drawn on a tab.


ACNPortsTab::ACNPortsTab( QWidget* pParent ) : QWidget( /*pParent*/ )
{
m_parent = reinterpret_cast<EditACNDlg*>( pParent );
m_bIsInitialized = false;

ui.setupUi( this );

InitializeTabUI(); // This call initializes UI elements & fills in data

} // ACNPortsTab::ACNPortsTab(


marcel, I thought that by using QTabWidget all non-visible forms were hidden by default. The constructor would set up the ui elements (and maybe fill in data) but no drawing would occur until that tab was selected, no?
Previously I updated the form data in each particular forms' constructor. I now delay that until the form is selected the first time. This seems to make little noticable difference.
My most complicated form contains 50+ items (QLineEdit, QComboBox, QPushButton, QLabel).
Thanks again for your help!

marcel
9th May 2007, 06:21
You can try setting the attribute WA_WState_ExplicitShowHide for the forms.
Also, I recommend hiding the forms that are not visible( index > 0 ) immediately after creating them ( after calling new ).
If you look at the QTabWidget + QStackedWidget + QStackedLayout +QLayout sources you can see that there are some things that are not done when an widget is not visible in the sense of isVisible - isHidden() is true.

Setting that flag may help a bit, knowing that you show them manually and being sure that no one shows them before you call show().

Apart from this, does the GUI thread controls the connections to those devices. If they are so many, perhaps it is a too heavy load on the thread. Have you thought creating a thread that manages the connections and updates the interface? This way the GUI thread would have more time to do it's "real" job.

Regards

mclark
9th May 2007, 15:57
marcel, thanks for the hiding hints, I'll try them today.

As for the device connections, they are maintained by a separate library. After the devices are discovered and connected our thread monitor shows very little activity ( all that's happening are ACKs to each device to keep the connection alive - which takes very little bandwidth ). The processor appears to be idling.

When the edit dialog is invoked the processor spikes to between 80 and 100 percent which corresponds to the spike in the GUI thread. The app is being run on a test network with no other activity and the machine the app is running on is only running the app.

While I may be weak in thread issues, even I can see that something I'm doing is causing the GUI thread to starve the other threads while drawing the dialog.

BTW, there are similar issues when scrolling through the 200+ member QTableWidget. This particular table has 15 cells: 1 containing an icon
7 containing text
1 hidden
5 containing QComboBox derived objects
1 containing a QPushButton derived object
A couple cells have tooltips and one has a data pointer attached and some, at times, may be read-only. Could this be too much too handle when scrolling through a 200+ member table?

Regards

marcel
9th May 2007, 19:30
Here's a crazy idea... that might just not work.
When you start the thread(s) that maintain the connections, set a higher priority, like start( QThread::TimeCriticalPriority ) or start( QThread::HighestPriority ).

mclark
9th May 2007, 19:56
Not so crazy, Marcel. The guy that maintains those libraries has given me a version with increased thread priority. While it makes the response better, it doesn't entirely solve the problem.

marcel
9th May 2007, 20:06
m_tabACN_Gen = new ACNGenTab( this );
m_tabWidget->addTab( m_tabACN_Gen, tr(GENERAL_TAB.toAscii().data()) );

m_tabACN_Ports = new ACNPortsTab( this );
m_tabWidget->addTab( m_tabACN_Ports, tr(PORTS_TAB.toAscii().data()) );

m_tabACN_AIP = new ACNAIPTab( m_pNode, m_pMainWin, this );
m_tabWidget->addTab( m_tabACN_AIP, tr("AIP") );


I just noticed this. You create the m_TabACN* forms with this as parent ( meaning the dialog ). But the parent should be m_tabWidget, otherwise the dialog wil lsend all kinds of events to the forms. You should really modify this.

You can also try updatesEnabled( false ) on the forms after you create them . This wil make them not receive resize or paint events. You enable the updates just before making them visible.

mclark
14th May 2007, 23:53
It appears that the boosted priority background thread and Marcel's parenting suggestion (along with taking numerous closer looks at my code) has made things acceptable. Some of the drawing is a little sluggish but the devices don't regularly go offline now.

wysota and marcel, thank's so much for your time!