The motivating scenario: I'm working on a Qt/QWidgets C app whose GUI is largely arranged into tabs, via a hierarchy of QTabWidget
s. Many of these tabs are various agglomerations of the same content, (e.g. there's a tab with widget A
and B
, a tab with widget B
and C
, and so on).
I have one particular widget class W
which is a fairly heavyweight GUI object and it appears in many (but not all) of the tabs. Currently I handle that by simply creating a separate object of the W
class for each tab I want it to appear in, and that basically works, but it's not 100% satisfactory for a couple of reasons:
- Since the widget is heavy, creating a number of instances of it slows down the GUI's creation on startup, and uses more system resources than I would like.
- Every time the user changes the layout/state of the widget in one tab, I have to manually echo that change to all of its "clones" in the other tabs; otherwise the user will notice the different states of the different W widgets as he moves back and forth from one tab to another. This is doable, but it's a maintenance and testing headache.
So what I'd like to do is create just a single instance of W
and have it magically appear in its expected location within whichever tab is currently visible. Since only one tab with W
should ever be visible at one time, it seems like a single W
instance ought to be enough to accomplish that.
I thought about making a lightweight proxy/container-widget of some sort, and overriding its showEvent()
method to setParent()
the real W
object to be its child as necessary; I think that might work, or it might turn out to be full of gotchas, so I thought I'd ask first if anyone else knows of a more elegant or better-supported way to accomplish the same result.
CodePudding user response:
#include <QApplication>
#include <QLabel>
#include <QMap>
#include <QSet>
#include <QStackedLayout>
#include <QTabWidget>
#include <QWidget>
/** This is a special container-class that holds a single target widget so that the target widget can be placed
* into more than one QTabWidget at a time. This widget will handle moving the target widget around from proxy
* to proxy as tabs are shown, so that instead of having to create N identical widgets, we can just create one
* target-widget and have it jump from tab to tab as necessary.
*/
class TabProxyWidget : public QWidget
{
public:
/** Constructor
* @param optTargetWidget if non-NULL, this will be passed to SetTargetWidget(). Defaults to NULL.
*/
TabProxyWidget(QWidget * optTargetWidget = NULL)
: _layout(new QStackedLayout(this))
, _targetWidget(NULL)
{
SetTargetWidget(optTargetWidget);
}
virtual ~TabProxyWidget() {SetTargetWidget(NULL);}
/** Set the widget that we want to be a proxy for
* @param optTargetWidget the widget we will proxy for, or NULL to disassociate us from any target widget
* @note the same pointer for (optTargetWidget) can (and should!) be passed to multiple TabProxyWidget objects
*/
void SetTargetWidget(QWidget * optTargetWidget);
virtual void showEvent(QShowEvent *);
virtual bool eventFilter(QObject * o, QEvent * e);
private:
void AdoptTargetWidget();
void UpdateSizeConstraints();
QStackedLayout * _layout;
QWidget * _targetWidget;
};
static QMap<QWidget *, QSet<TabProxyWidget *> > _targetWidgetToProxies;
void TabProxyWidget :: SetTargetWidget(QWidget * targetWidget)
{
if (targetWidget != _targetWidget)
{
if (_targetWidget)
{
_targetWidget->removeEventFilter(this);
QSet<TabProxyWidget *> * proxiesForTargetWidget = _targetWidgetToProxies.contains(_targetWidget) ? &_targetWidgetToProxies[_targetWidget] : NULL;
if ((proxiesForTargetWidget == NULL)||(proxiesForTargetWidget->isEmpty()))
{
printf("TabProxyWidget::SetTargetWidget(NULL): can't proxies-table for target widget %p is %s!\n", targetWidget, proxiesForTargetWidget?"empty":"missing");
exit(10);
}
(void) proxiesForTargetWidget->remove(this);
if (proxiesForTargetWidget->isEmpty())
{
(void) _targetWidgetToProxies.remove(_targetWidget);
delete _targetWidget;
}
else if (dynamic_cast<TabProxyWidget *>(_targetWidget->parentWidget()) == this)
{
proxiesForTargetWidget->values()[0]->AdoptTargetWidget(); // hand him off to another proxy to for safekeeping
}
}
_targetWidget = targetWidget;
if (_targetWidget)
{
if (_targetWidgetToProxies.contains(_targetWidget) == false) _targetWidgetToProxies[_targetWidget] = QSet<TabProxyWidget *>();
_targetWidgetToProxies[_targetWidget].insert(this);
if ((isHidden() == false)||(_targetWidget->parentWidget() == NULL)||(dynamic_cast<TabProxyWidget *>(_targetWidget->parentWidget()) == NULL)) AdoptTargetWidget();
UpdateSizeConstraints();
_targetWidget->installEventFilter(this);
}
}
}
bool TabProxyWidget :: eventFilter(QObject * o, QEvent * e)
{
if ((o == _targetWidget)&&(e->type() == QEvent::Resize)) UpdateSizeConstraints();
return QWidget::eventFilter(o, e);
}
void TabProxyWidget :: UpdateSizeConstraints()
{
if (_targetWidget)
{
setMinimumSize(_targetWidget->minimumSize());
setMaximumSize(_targetWidget->maximumSize());
setSizePolicy (_primaryWidget->sizePolicy());
}
else
{
setMinimumSize(QSize(0,0));
setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
setSizePolicy (QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
}
}
void TabProxyWidget :: showEvent(QShowEvent * e)
{
AdoptTargetWidget();
QWidget::showEvent(e);
if (_targetWidget) _targetWidget->show();
}
void TabProxyWidget :: AdoptTargetWidget()
{
if ((_targetWidget)&&(_targetWidget->parentWidget() != this))
{
QLayout * layout = _targetWidget->layout();
if (layout) layout->removeWidget(_targetWidget);
_targetWidget->setParent(this);
_layout->addWidget(_targetWidget);
}
}
static void SetWidgetBackgroundColor(QWidget * w, const QColor bc)
{
QPalette p = w->palette();
p.setColor(QPalette::Window, bc);
w->setAutoFillBackground(true);
w->setPalette(p);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
QTabWidget * tabWidget = new QTabWidget;
tabWidget->setWindowTitle("Proxy Widget test");
QWidget * proxyMe = new QLabel("Shared Widget!");
SetWidgetBackgroundColor(proxyMe, Qt::green);
int counter = 0;
for (int i=0; i<5; i )
{
QWidget * nextTab = new QWidget;
QBoxLayout * tabLayout = new QBoxLayout(QBoxLayout::TopToBottom, nextTab);
const int numAbove = rand()%3;
for (int i=0; i<numAbove; i ) tabLayout->addWidget(new QLabel(QString("Unshared label #%1 above").arg( counter)));
tabLayout->addWidget(new TabProxyWidget(proxyMe));
const int numBelow = rand()%3;
for (int i=0; i<numBelow; i ) tabLayout->addWidget(new QLabel(QString("Unshared label #%1 below").arg( counter)));
tabWidget->addTab(nextTab, QString("Tab %1").arg(i 1));
}
tabWidget->show();
return app.exec();
}