Search code examples
c++user-interfacewidgetqt4qt-creator

Custom widget in Qt Designer with pre-existing layout in frame


A project uses custom frame widget that implements a kind of "cover" that hides widgets (think of it as of security cover that prevents pressing buttons).This is a required visual design. Qt version is 4.6

#ifndef CQFRAME_H
#define CQFRAME_H

#include <QFrame>

#include <QtDesigner/QDesignerExportWidget>

//! [0] //! [1]
class CQFrame : public QFrame   
// our agreement about "custom" widgets is to start them with CQ
{
    Q_OBJECT
    Q_ENUMS(FrameColorStyle)

    Q_PROPERTY(FrameColorStyle colorStyle READ getColorStyle WRITE setColorStyle)
    Q_PROPERTY(bool border READ border WRITE setBorder)
//! [0]
private:
    bool curCover;
    QFrame *frameCover;
public:
    enum FrameColorStyle  {fcDark, fcLight, fcTransparent, fcSystemDefault, fcRed, fcGreen, fcBlue};

    CQFrame(QWidget *parent = 0);

    void setCoverPropertie();
    void setCover(bool state);
protected:
    void resizeEvent(QResizeEvent *event);
    FrameColorStyle m_colorStyle;
    QString pstylebord;
    bool m_border;
//! [2]
};
//! [1] //! [2]

#endif

I omitted getters and setters that are irrelevant to the problem. Here is implementation:

void CQFrame::setCoverPropertie()
{
    QString str, strAlpha, gradient1, gradient2;
    strAlpha.setNum(200);
    gradient1 = "rgba("+str.setNum(cwDisableColor.red())+", "
                +str.setNum(cwDisableColor.green())
                +", "+str.setNum(cwDisableColor.blue())
                +" ," +strAlpha+ " )";
    gradient2 = "rgba("+str.setNum(cwLbColor.red())+", "
                +str.setNum(cwLbColor.green())+", "
                +str.setNum(cwLbColor.blue())+" ," +strAlpha+ " )";

    QStackedLayout *stackedLayout = new QStackedLayout(this);
    frameCover  = new QFrame(this);     
    frameCover->setGeometry(rect());
    frameCover->setStyleSheet("QFrame{border:5px solid  "+strLbColor+"; "
                                  "border-radius: 10px; background-color: "
                                  "qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.5, stop: 0 "
                                  +gradient1+" , stop: 1 "+gradient2+"); }");

    stackedLayout->addWidget(frameCover);
    stackedLayout->setStackingMode(QStackedLayout::StackAll);
}

void CQFrame::setCover(bool state)
{
    frameCover->setVisible(curCover = state);
}

void CQFrame::resizeEvent(QResizeEvent *event)
{
    if (curCover)
        frameCover->setGeometry(rect());
}

The design isn't mine, I was asked to fix strange visual glitches it experiences. This "frame" is used in Qt designer as one of widgets. After a while suddenly everything resizes, which prompted question "what is wrong with this code". Qt fires warning about attempt to add layout while one already exist: I suppose that may cause a problem, because a frame must have only one layout at time? Code generated by Qt Creator looks something like

void setupUi(CQFrame *CQWnd1T2SeparateONForm)
    {
        if (CQWnd1T2SeparateONForm->objectName().isEmpty())
            CQWnd1T2SeparateONForm->setObjectName(QString::fromUtf8("CQWnd1T2SeparateONForm"));
        CQWnd1T2SeparateONForm->resize(735, 241);
        QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
        sizePolicy.setHorizontalStretch(0);
        sizePolicy.setVerticalStretch(0);
        sizePolicy.setHeightForWidth(CQWnd1T2SeparateONForm->sizePolicy().hasHeightForWidth());
        CQWnd1T2SeparateONForm->setSizePolicy(sizePolicy);
        gridLayout = new QGridLayout(CQWnd1T2SeparateONForm); // warning here
}

There is similar problem with standard QMainWindow which always got own "special" layout, which Qt Creator solves automatically, by adding a central widget to the layout and everything else is added to that widget. What I don't know that is how to simulate same behavior with a custom widget with Qt Creator plugin. Or what alternative design for CQFrame can be used. CQFrame reused in dozen project, in about 30+ panels, so reuse of code for them all is a strict requirement. Current plugin is very basic:

class QDESIGNER_WIDGET_EXPORT CQFramePlugin : public QObject,
                             public QDesignerCustomWidgetInterface
{
    Q_OBJECT
    Q_INTERFACES(QDesignerCustomWidgetInterface)

public:
    CQFramePlugin(QObject *parent = 0);

    bool isContainer() const;
    bool isInitialized() const;
    QIcon icon() const;
    QString domXml() const;
    QString group() const;
    QString includeFile() const;
    QString name() const;
    QString toolTip() const;
    QString whatsThis() const;
    QWidget *createWidget(QWidget *parent);
    void initialize(QDesignerFormEditorInterface *core);

private:
    bool initialized;
};

.cpp for it:

#include "cqframe.h"
#include "cqframeplugin.h"

#include <QtPlugin>

CQFramePlugin::CQFramePlugin(QObject *parent)
    : QObject(parent)
{
    initialized = false;
}

void CQFramePlugin::initialize(QDesignerFormEditorInterface * /* core */)
{
    if (initialized)
        return;

    initialized = true;
}

bool CQFramePlugin::isInitialized() const
{
    return initialized;
}

QWidget *CQFramePlugin::createWidget(QWidget *parent)
{
    return new CQFrame(parent);
}

QString CQFramePlugin::name() const
{
    return "CQFrame";
}

QString CQFramePlugin::group() const
{
    return "CustomWidgets";
}

QIcon CQFramePlugin::icon() const
{
    return QIcon(":/Resources/frame_icon.png");
}

QString CQFramePlugin::toolTip() const
{
    return "";
}

QString CQFramePlugin::whatsThis() const
{
    return "";
}

bool CQFramePlugin::isContainer() const
{
    return true;
}

QString CQFramePlugin::domXml() const
{
    return "<ui language=\"c++\">\n"
           " <widget class=\"CQFrame\" name=\"Frame\">\n"
           "  <property name=\"geometry\">\n"
            "   <rect>\n"
            "    <x>0</x>\n"
            "    <y>0</y>\n"
            "    <width>120</width>\n"
            "    <height>80</height>\n"
            "   </rect>\n"
            "  </property>\n"
           " </widget>\n"
           "</ui>";
}

QString CQFramePlugin::includeFile() const
{
    return "cqframe.h";
}

Solution

  • So I haven't worked with QT yet, but I'm going to gie it a try. First thing, I think, is that you should use the QStackedLayout to cover the widgets (source and QT manual). But you need to have a single stack.

    So the stack should be a private member of CQFrame. E.g.

    class CQFrame : public QFrame   
    {
    [...]
    private:
    
        QWidget* _frame; // Frame to cover.
        QFrame* _frameCover;
        QStackedLayout* _stackedLayout;
    [...]
    

    And probably:

    CQFrame::~CQFrame()
    {
        delete _stackedLayout;
        delete _frameCover;
    }
    

    Then you could already initialize everything in the constructor

    CQFrame::CQFrame(QWidget* parent = 0, QWidget* frame)
        : QFrame(parent)
        , _frame(frame)
        , _frameCover(new QFrame(this))
        , _stackedLayout(new QStackedLayout(this))
    {
            _frameCover->setGeometry(rect());
            [...]
            _stackedLayout->addWidget(frame);
            _stackedLayout->addWidget(frameCover);
            //default:_stackedLayout->setStackingMode(QStackedLayout::StackOne);
    }
    

    You could then switch between widgets in the stack using

    void CQFrame::SetCover(bool state)
    {
        if (state) _stackedLayout->setCurrentWidget(_frameCover);
        else _stackedLayout->setCurrentWidget(_frame);
    }
    

    Maybe this helps you.

    edit2: I removed this code, as it was incorrect, both in coding format, as in idea So I checked the QT sources QStackedLayout.cpp and QLayout and it seems a QWidget can have only one layout. If you add another layout, you get an error. Furthermore, a QStackedLayout is a QLayout, is QObject. That could indicate it is automatically removed?

    I'm not sure of the cover is implemented in QT as it should. It seems like you have a QWidget you want to cover, on top of which you put a QStackedLayout that is not used as designed i.e. switching stack objects, on top of which you put a new QWidget that is made visible or not. And maybe that bottom QWidget is already in a (stacked)layout, etc.