0

I am currenctly using Qt 5.7 and I have subclassed QPushButton with the basic idea of using Qt Style sheets on QML side. Here is header file:

#ifndef LTPUSHBUTTON_H
#define LTPUSHBUTTON_H

#include <QWidget>
#include <QPushButton>
#include <QString>

/**
 * @class Modified push button class
 */
class LtPushButton : public QPushButton
{
    Q_OBJECT

public:
    /**
     * @brief Constructor
     * @param parent
     */
    LtPushButton(const QString& ltText=QString(),
                 QWidget* const parent=Q_NULLPTR);

    /**
     * @brief Method for settings CSS style from QML side
     * @param ltCSSStyle
     */
    Q_INVOKABLE void ltSetCSSStyle(const QString& ltCSSStyle);
};

#endif // LTPUSHBUTTON_H

and its implementation:

#include "ltpushbutton.h"

LtPushButton::LtPushButton(const QString &ltText,
                           QWidget* const parent)
    : QPushButton(parent)
{
    this->setText(ltText);
}   // constructor


void LtPushButton::ltSetCSSStyle(const QString& ltCSSStyle)
{
    this->setStyleSheet(ltCSSStyle);
}   // ltSetCSSStyle

The LtPushButton type is registered in main.cpp:

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlEngine>

#include "ltpushbutton.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication app(argc, argv);
    QQmlApplicationEngine engine;

    qmlRegisterType<LtPushButton>("com.testapp.gui",
                                  1,
                                  0,
                                  "LtPushButton");

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);

    engine.load(url);

    return app.exec();
}

and when I try to set stylesheet of window in main.qml:

import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3

import com.testapp.gui 1.0

Window
{
    width: 640
    height: 480

    visible: true

    title: qsTr("Hello World")

    color: "red"

    GridLayout
    {
        anchors.fill: parent

        rows: 2
        columns: 2

        LtPushButton
        {
            id: ltUpperLeftButton

            Layout.fillWidth: true
            Layout.fillHeight: true
            Layout.alignment: Qt.AlignHCenter|Qt.AlignVCenter

            text: qsTr("One");
        }   // LtPushButton

        Component.onCompleted:
        {
            ltUpperLeftButton.ltSetCSSStyle("border-top-left-radius: 20px;
                                             border-top-right-radius: 20px;
                                             border-bottom-left-radius: 20px;
                                             border-bottom-right-radius: 20px;
                                             background-color: #0047bd;
                                             border: 2px solid #0047bd;
                                             color: #ffffff;
                                             outline: none;");
        }   // Component.onCompleted
    }   // GridLayout
}   // Window

the app crashes. Basicly, I am trying to implement QML Button with arbitrary number of radial corners, for example, lower right corners is radially cornered, other corners are perpendicular:

ButtonConfirm

or lower left is radially cornered, other corners are perpendicular:

ButtonCancel

Same applies to upper corners.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
KernelPanic
  • 2,328
  • 7
  • 47
  • 90
  • Well, you can not. The painting system in QML is different from QtWidgets, besides QPushButton expects a QWidget as a parent but in the QML it is pointing out that the parent is GridLayout that is not a QWidget. You can embed a QML in QWidget using QQuickWidgets but not vice versa – eyllanesc Jun 03 '19 at 11:55
  • 1
    you're confusing with 2 incompatible concepts. QtQuick uses [Scene Graph](https://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html) when QPushButton is QtWidgets – folibis Jun 03 '19 at 11:57

1 Answers1

1

You can't embed a Qt widget in a QML like that. QML uses a scene graph (it was possible to use QGraphicsProxyWidget with Qt Quick 1 which used a QGraphicsView.

You have to use a QQuickPaintedItem, now.

But, making a button with a radial corner is quite easy directly in QML by using Shape.

A quick example:

Button {
        id: button
        background: Shape {
            ShapePath {
                id: shape
                property int angleRadius: 12
                strokeWidth: 4
                fillColor: "red"
                startX: 0; startY: 0
                PathLine { x: button.width; y: 0 }
                PathLine { x: button.width; y: button.height - shape.angleRadius }
                PathArc {
                        x: button.width - shape.angleRadius; y: button.height
                        radiusX: shape.angleRadius
                        radiusY: shape.angleRadius
                    }
                PathLine { x: 0; y: button.height }
                PathLine { x: 0; y: 0 }
            }
        }
        text: "Confirm"
    }

If you don't want to use Shape, you could use basic Rectangle (without border): one with rounded corners and two others to mask some corners.

For example:

Button {
    id: button2
    background: Rectangle {
        id: bg
        radius: 8
        color: "green"
        Rectangle {
            width: bg.width
            height: bg.radius
            x: 0
            y: 0
            color: bg.color
        }
        Rectangle {
            width: bg.radius
            height: bg.height
            x: 0
            y: 0
            color: bg.color
        }
    }
    text: "Confirm"
}

And if you really want to use a C++ class, use QQuickPaintedItem:

class Button : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QColor color READ color WRITE setColor)
    Q_PROPERTY(QString text READ text WRITE setText)
public:
    Button(QQuickItem* parent=nullptr): QQuickPaintedItem(parent) {
        setAcceptedMouseButtons(Qt::LeftButton);
    }

    virtual void paint(QPainter* painter) override
    {
        QPainterPath path;
        path.setFillRule(Qt::WindingFill);
        path.addRoundRect(contentsBoundingRect(), 90, 90);
        path.addRect(0, 0, width(), height() / 2);
        path.addRect(0, 0, width() / 2, height());
        painter->save();
        painter->setPen(Qt::NoPen);
        painter->setBrush(color());
        painter->drawPath(path);
        painter->restore();
    }

    QColor color() const { return bgColor; }
    QString text() const { return contentText; }
public slots:
    void setColor(QColor const& color) { bgColor = color; }
    void setText(QString const& text) { contentText = text; }

signals:
    void clicked();
private:
    QColor bgColor;
    QString contentText;

    QElapsedTimer pressedTimer;

    virtual void mousePressEvent(QMouseEvent* event) override
    {
        if (event->button() != Qt::MouseButton::LeftButton)
        {
            pressedTimer.invalidate();
            return;
        }
        pressedTimer.restart();
    }

    virtual void mouseReleaseEvent(QMouseEvent* /*event*/) override
    {
        if (pressedTimer.elapsed() < 200)
            clicked();
        pressedTimer.invalidate();
    }
};


// In main.cpp
qmlRegisterType<Button>("my.app", 1, 0, "MyButton");

// main.qml
MyButton {
    text: "Cancel"
    color: "blue"
    width: 60
    height: 30
    onClicked: console.log("Clicked")
}
Dimitry Ernot
  • 6,256
  • 2
  • 25
  • 37