Home > Enterprise >  Is there a way to place a widget into more than one of the tabs of a QTabWidget?
Is there a way to place a widget into more than one of the tabs of a QTabWidget?

Time:01-19

The motivating scenario: I'm working on a Qt/QWidgets C app whose GUI is largely arranged into tabs, via a hierarchy of QTabWidgets. 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:

  1. 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.
  2. 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:

With demo screenshot

#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();
}
  • Related