3
votes

I'm trying to make a QT application that works as a Dungeons and Dragons character sheet. As a result, I need to have a widget inside another widget. For example, a stat block needs to look like this:

That means it needs a QLabel for the text, a QLineEdit for entering the bottom number, and another QLabel for the modifier (+3). It also needs to have an SVG file as a background.

My problem is this: Is there any way to make such a custom widget that incorporates all three needed widgets and the background into one singular widget? My current code currently looks like this:

statBlock.hpp:

class QLineEdit;
class QLabel;
class QSvgRenderer;
class QSize;

class StatBlockWidget : public QWidget {
    Q_OBJECT

public:
    StatBlockWidget(const QString& name, QWidget* parent = 0);

    ~StatBlockWidget();


protected:
    QSize sizeHint() const override;
    QSize minimumSizeHint() const override;

private:
    QLineEdit* abilityScoreEntry;
    AbilityScoreModDisplay* abilityModifierDisplay; // Custom widget that automatically shows the correct modifier for a stat's number.
    QLabel* abilityName;
    QSvgRenderer* svgRenderer;
    QLabel* background;
};

statBlock.cpp:

#include "statBlock.hpp"

#include <QLineEdit>
#include <QLabel>
#include <QValidator>
#include <QSvgRenderer>
#include <QPainter>
#include <QPaintEvent>
#include <QPixmap>
#include <QSizePolicy>

StatBlockWidget::StatBlockWidget(const QString& name, QWidget* parent):
    QWidget(parent)
{
    // Properties of this widget
    this->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
    this->resize(100, 100);

    // Create child widgets
    abilityModifierDisplay = new AbilityScoreModDisplay(this);
    abilityName = new QLabel(this);
    abilityScoreEntry = new QLineEdit(this);
    svgRenderer = new QSvgRenderer(QStringLiteral(":/charSheet/assets/statblock.svg"), this);
    background = new QLabel(this);

    QImage backgroundImage{100, 100, QImage::Format_A2RGB30_Premultiplied};

    backgroundImage.fill(Qt::transparent);

    // Set attributes
    abilityModifierDisplay->raise();

    abilityScoreEntry->raise();
    abilityScoreEntry->setValidator(new QIntValidator(1, 30, this));

    abilityName->raise();
    abilityName->setText(name);
    // Render the background svg onto the widget
    QPainter painter(&backgroundImage);
    svgRenderer->render(&painter);

    background->setPixmap(QPixmap::fromImage(backgroundImage));

    // Set styles and positions for inputs and outputs

    abilityScoreEntry->setFixedWidth(80);
    abilityScoreEntry->setAlignment(Qt::AlignCenter);
    abilityScoreEntry->setStyleSheet("background-color: transparent;"
                                     "color: black;"
                                     "font-size: 35px;"
                                     "border: 1px solid white;");
    QPoint centerPoint = this->rect().center() - abilityScoreEntry->rect().center();
    centerPoint.setY(centerPoint.y() - 10);
    abilityScoreEntry->move(centerPoint);

    abilityModifierDisplay->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
    abilityModifierDisplay->setFixedWidth(this->width());
    abilityModifierDisplay->move(0,-1); // Needed to fix center the text vertically.
    abilityModifierDisplay->setStyleSheet("background-color: transparent;"
                                          "color: black;"
                                          "font-size: 14px");

    abilityName->setFixedWidth(80);
    abilityName->setAlignment(Qt::AlignCenter);
    abilityName->setStyleSheet("background-color: transparent;"
                               "color: black;"
                               "font-size: 12px;");
    centerPoint = this->rect().center() - abilityName->rect().center();
    centerPoint.setY(centerPoint.y() + 40);
    abilityName->move(centerPoint);

    // Set fixed size for this widget
    this->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));

    // Connect Signals and Slots
    connect(abilityScoreEntry, SIGNAL(textChanged(const QString&)), abilityModifierDisplay, SLOT(updateModifer(const QString&)));
}

StatBlockWidget::~StatBlockWidget() {}

QSize StatBlockWidget::sizeHint() const {
    return QSize(100,100);
}

QSize StatBlockWidget::minimumSizeHint() const {
    return QSize(100,100);
}

What I'm trying to do in the code is create a custom widget with 4 child widgets, 3 for the text and numbers, and a QLabel as a background (and a QSvgRenderer to render the svg onto the QLabel). However, this approach is very hard to modify, and it will be harder to do for other aspects of the sheet.

Is there any way in QT that allows me to place widgets on a certain location of its parent widget, that is easy to adjust? I've looked into layouts, but those don't seem to be for a general placement of widgets (ie. in a line or grid), whereas I want to place the widgets very specifically on its parent (say, at 40% mark of its height and width). How could I do this?

2
Apparently your widget has a fixed size, so why don't just stack labels + line-edit in vertical layout, with text-align center, forcing their height to place them where you want them, and eventually playing with padding to ensure they wrap text instead of overflowing on your SVG-borders ?ymoreau
@ymoreau That would work for the example given, but I probably wasn't clear enough with my question. I wanted a widget to be placed anywhere on a parent, even not just in a straight line.Andrew P.
It really depends on how much anywhere you mean, most of the time UI just requires to be aligned/centered. Even if you need to translate everything from 10px to the left for example, you can play with the padding of your QWidget container or add some spacings in your layout etc. When you really need to display many things with different positions you might be more in the QGraphicsScene use-case than just displaying a composed-widget.ymoreau

2 Answers

2
votes

You must create a function that updates the position relative to the size change, for this the function updatePosition has been created. This method must be called in the resizeEvent event as shown below:

#ifndef POSITIONWIDGET_H
#define POSITIONWIDGET_H

#include <QLabel>
#include <QLineEdit>
#include <QResizeEvent>
#include <QWidget>

class PositionWidget : public QWidget
{
    Q_OBJECT

public:
    PositionWidget(QWidget *parent = 0):QWidget(parent){
        label = new QLabel("LABEL", this);
        lineEdit = new QLineEdit(this);
    }
    ~PositionWidget(){
    }
protected:
    void resizeEvent(QResizeEvent *event){
        updatePosition(label, 0.5, 0.5); //50% , 50%
        updatePosition(lineEdit, 0.3, 0.7); //30% , 70%
        QWidget::resizeEvent(event);
    }
private:
    QLabel *label;
    QLineEdit *lineEdit;

    void updatePosition(QWidget *widget, float xscale, float yscale){
        int w = size().width();
        int h = size().height();
        widget->move( QPoint(w*xscale, h*yscale) - widget->rect().center());
    }
};

#endif // POSITIONWIDGET_H

The complete example can be found at the following link.

0
votes

Because you have a fixed size on your widget, you can make it simple with fixed heights on your elements, here is an example of the layout :

auto widget = new QWidget;

widget->setFixedSize(100, 100);
// set your SVG background on widget

auto layout = new QVBoxLayout;
layout->setSpacing(0);
layout->setMargin(0);

auto abilityName = new QLabel(tr("Strength"));
abilityName->setFixedHeight(25);
abilityName->setAlignment(Qt::AlignCenter);
abilityName->setStyleSheet("font-size: 12px"); // Background is transparent by default
layout->addWidget(abilityName);

auto abilityModifierDisplay = new QLabel("+3");
abilityModifierDisplay->setFixedHeight(40);
abilityModifierDisplay->setAlignment(Qt::AlignCenter);
abilityModifierDisplay->setStyleSheet("font-size: 15px");
layout->addWidget(abilityModifierDisplay);

auto abilityScoreEntry = new QLineEdit("16");
abilityScoreEntry->setFixedHeight(25);
abilityScoreEntry->setAlignment(Qt::AlignCenter);
abilityScoreEntry->setStyleSheet("border:none; font-size: 11px");
layout->addWidget(abilityScoreEntry);

// That is your margin from bottom so abilityScoreEntry is not vertically centered
// from the bottom, but centered in the area of the background you want it to be
layout->addSpacing(10);

widget->setLayout(layout);

To have a customized widget to encapsulate this, just make what you did : inherit from QWidget, create all the sub-elements and layout in your constructor, add signal/slots for modifiers.