How to redraw a button ??
-
I want to display a button that will have the standard functionality of a button (QPushButton), but at the same time have a different appearance
But for some reason they do not allow you to output a class that inherits from QPushButton
here is the code (I can't figure out what I'm doing wrong):
CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(myButton VERSION 0.1 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) set(PROJECT_SOURCES main.cpp mainwindow.cpp mainwindow.h mainwindow.ui custombutton.cpp custombutton.h lineitem.cpp lineitem.h ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_add_executable(myButton MANUAL_FINALIZATION ${PROJECT_SOURCES} ) # Define target properties for Android with Qt 6 as: # set_property(TARGET myButton APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR # ${CMAKE_CURRENT_SOURCE_DIR}/android) # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation else() if(ANDROID) add_library(myButton SHARED ${PROJECT_SOURCES} ) # Define properties for Android with Qt 5 after find_package() calls as: # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") else() add_executable(myButton ${PROJECT_SOURCES} ) endif() endif() target_link_libraries(myButton PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) set_target_properties(myButton PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} ) if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(myButton) endif()
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.cpp
#include "mainwindow.h" #include "./ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { _line = new lineItem(this); ui->setupUi(this); ui->gridLayout->addItem(_line); } MainWindow::~MainWindow() { delete ui; }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include "lineitem.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; lineItem* _line; }; #endif // MAINWINDOW_H
custombutton.cpp
#include "custombutton.h" customButton::customButton(QPushButton *parent) : QPushButton{parent} { _myPushButton = qobject_cast<QPushButton*>(parent); } void customButton::setPosition(float radiusPos, float angle) { if(radiusPos<0) _rPos = 0; else _rPos = radiusPos; if(angle>360) _angle = 360; else if(radiusPos<0) _angle = 0; else _angle = angle; _myPushButton->update(); } QPointF customButton::getCenterPoint(float radPos, float angle) { float r = getRadius(_myPushButton->rect()); float xx=cos(qDegreesToRadians(angle+90))*r; float yy=sin(qDegreesToRadians(angle+90))*r; QPointF pt; xx=_myPushButton->rect().center().x()-xx*radPos; yy=_myPushButton->rect().center().y()-yy*radPos; pt.setX(xx); pt.setY(yy); return pt; } void customButton::setColors(const QColor &substrate, const QColor &item, const QColor &outline) { _colorSubstrate = substrate; _colorItem = item; _colorOutline = outline; } void customButton::setValueRange(float minValue, float maxValue) { _minValue = minValue; _maxValue = maxValue; } void customButton::setDegreeRange(float minDegree, float maxDegree) { _minDegree = minDegree; _maxDegree = maxDegree; } void customButton::setSubstrate(bool val) { _drawSubstrate = val; } void customButton::setThicknessOutline(float val) { _thicknessOutline = val; } void customButton::setOutline(bool val) { _drawOutline = val; } void customButton::setShadow(bool val) { _drawShadow = val; } float customButton::getRadius(const QRectF &tmpRect) { float r = 0; if(tmpRect.width() < tmpRect.height()*_wh) r = tmpRect.width()/(2.0*_wh); else r = tmpRect.height()/2.0; return r; } float customButton::getDegFromValue(float val) { float a = (_maxDegree-_minDegree)/(_maxValue-_minValue); float b = -a*_minValue+_minDegree; return a*val+b; } void customButton::setScaleFactor(float val) { _scaleFactor = val; } void customButton::setThickness(float val) { _thickness = val; } void customButton::setProportions(float wh) { _wh = wh; }
custombutton.h
#ifndef BUTTONITEM_H #define BUTTONITEM_H #include <QObject> #include <QButtonGroup> #include <QPushButton> #include <QPainter> #include <QColor> #include <QFont> #include <QDebug> #include <QPainterPath> #include <QPointF> #include <QRect> #include <QGraphicsBlurEffect> #include <QGraphicsItem> #include <QtMath> #include <QMessageBox> class customButton : public QPushButton { public: explicit customButton(QPushButton *parent = nullptr); virtual void draw(QPainter *p) = 0; /// /// \brief setPosition установить радиус (1=100%) и угол (в градусах) определяющий расположение item на виджете /// void setPosition(float radiusPos , float angle); /// /// \brief getPoint расчитать центральную точку /// QPointF getCenterPoint(float radPos, float angle); /// /// \brief setColors: установить три цвета ( подложки, основной и обводки) для item /// void setColors(const QColor &substrate, const QColor &item, const QColor &outline); /// /// \brief setValueRange установить диапазон изменения значения /// void setValueRange(float minValue,float maxValue); /// /// \brief setDegreeRange установить диапазон изменения угла /// void setDegreeRange(float minDegree,float maxDegree); /// inner function /// /// \brief включить отрисовку подложки с заданным ранее цветом и прозрачностью /// void setSubstrate(bool val); /// /// \brief setThicknessOutline установить толщину обводки /// void setThicknessOutline(float val); /// /// \brief включить отрисовку подложки с заданным ранее цветом и прозрачностью /// void setOutline(bool val); /// /// \brief включить отрисовку "тени" /// void setShadow(bool val); /// /// \brief getRadius получить радиус окружности в которую вписан заданный прямоугольник /// float getRadius(const QRectF &tmpRect); /// /// \brief getDegFromValue /// float getDegFromValue(float val); /// /// \brief setScaleFactor установить коэффициент масштаба /// void setScaleFactor(float val); /// /// \brief setThickness установить толщину линии /// void setThickness(float val); /// /// \brief setProportions задать соотношение ширины к высоте в виджете с данным item /// void setProportions(float wh); // QWidget *_parentWidget; QPushButton *_myPushButton; QColor _colorSubstrate {Qt::black}; QColor _colorItem {Qt::black}; QColor _colorOutline {Qt::white}; QColor _colorCornerBound {Qt::darkRed}; QColor _colorShadowB {Qt::black}; QColor _colorShadowW {Qt::white}; QColor _colorKaracurtWhite {255, 255, 255, 255}; QColor _colorKaracurtYellow {255, 255, 0 , 255}; QColor _colorKaracurtSubstrate { 0, 0, 0 , 128}; QColor _colorKaracurtShadow { 0 , 0 , 0 , 200}; float _thicknessOutline {5}; float _thickness {3}; float _scaleFactor {1}; float _rPos {0.5}; float _angle {0.5}; float _minValue {0}; float _maxValue {100}; float _minDegree {0}; float _maxDegree {360}; bool _drawSubstrate {false}; bool _drawOutline {false}; bool _drawShadow {false}; float _wh {1}; signals: }; #endif // BUTTONITEM_H
lineitem.cpp
#include "lineitem.h" lineItem::lineItem(QPushButton *parent) : customButton(parent) { } void lineItem::draw(QPainter *p) { p->save(); QPointF center = getCenterPoint(_rPos, _angle); float r = getRadius(_myPushButton->rect())*_scaleFactor; if(_drawSubstrate) { p->setBrush(QBrush(_colorSubstrate)); p->drawEllipse(center,(int)(r), (int)(r)); } p->translate(center); p->setPen(Qt::NoPen); p->setBrush(QBrush(_color)); p->drawRect(-r/1.5, -r/1.5, r*1.34, r*1.34); QVector<QPointF> tmpPoints; tmpPoints.append(QPointF(0.0 - r*0.46 , 0.0 + r*0.2 )); tmpPoints.append(QPointF(0.0 - r*0.36 , 0.0 + r*0.2 )); tmpPoints.append(QPointF(0.0 - r*0.26 , 0.0 - r*0.15 )); tmpPoints.append(QPointF(0.0 - r*0.06 , 0.0 + r*0.45 )); tmpPoints.append(QPointF(0.0 + r*0.06 , 0.0 - r*0.4 )); tmpPoints.append(QPointF(0.0 + r*0.26 , 0.0 + r*0.25 )); tmpPoints.append(QPointF(0.0 + r*0.36 , 0.0 + r*0.1 )); tmpPoints.append(QPointF(0.0 + r*0.46 , 0.0 + r*0.1 )); QPainterPath path; path.addPolygon(tmpPoints); p->setBrush(Qt::NoBrush); p->setPen(QPen(_color_line, 2*_thickness, Qt::SolidLine)); p->drawPath(path); p->restore(); }
lineitem.h
#ifndef LINEITEM_H #define LINEITEM_H #include "custombutton.h" class lineItem : public customButton { public: lineItem(); explicit lineItem(QPushButton *parent = nullptr); virtual void draw(QPainter * p) override final; private: QColor _color {220, 0, 0, 227}; QColor _color_line {255,255,255,255}; }; #endif // LINEITEM_H
-
@timob256 said:
I have somehow already done inherited from qwidget
no, inheriting from QPushButton is fine, I'm talking about the constructor parameter that should be QWidget and not QPushButton.
I still didn't understand what exactly to do
Summarizing what I said:
- Change
explicit lineItem(QPushButton *parent = nullptr)
toexplicit lineItem(QWidget *parent = nullptr)
- remove the unnecessary
lineItem()
constructor - remove the unnecessary
QPushButton *_myPushButton
member - use your own geometry instead of parent's in your logic e.g. instead of
_myPushButton->rect()
userect()
. - override
paintEvent()
and paint there, not in customdraw()
method.
Is there an example ???
Of what? Of exactly what you're doing? I don't think so.
- Change
-
You have a constructor
explicit lineItem(QPushButton *parent = nullptr)
and you're calling it asnew lineItem(this)
, wherethis
is a MainWindow and not an instance of QPushButton.It's unusual to have a QPushButton parent. Usually you'd put plain QWidget as the parent type so you can make the widget a child of any other widgets, not just QPushButtons.
Btw. it's also weird to have both a default constructor with no parameters and another with single default parameter. The second one should be enough for both cases.
Another weird thing is code like this:
_myPushButton = qobject_cast<QPushButton*>(parent);
when parent already is a QPushButton. You don't even use any of QPushButton's methods, so you could just use plain QWidget as a parent. You don't need to store it either. All QWidgets already store their parent and you can access it viaparentWidget()
method. You should also always check if parent is null before you use it, because you yourself created a constructor that allows to pass nullptr and that's even the default parameter.I don't really get the custom button logic either. Why is it using its parent geometry? Shouldn't it be using its own? Any well behaving widget doesn't rely on existance of a parent at all. What if someone instantiates your widget as a top level widget without a parent?
Another thing is that a widget should draw itself in an overriden
paintEvent
method, which is called by the framework when the widget needs to be redrawn. Why do you have a customdraw
method that someone will have to always remember to call for you? -
@Chris-Kawa Thanks for the answer, but I still didn't understand what exactly to do. I have somehow already done inherited from
qwidget , but I didn't output actions (signal-slot) like "clicking" And "Hovering the mouse".Is there an example ???
-
@timob256 said:
I have somehow already done inherited from qwidget
no, inheriting from QPushButton is fine, I'm talking about the constructor parameter that should be QWidget and not QPushButton.
I still didn't understand what exactly to do
Summarizing what I said:
- Change
explicit lineItem(QPushButton *parent = nullptr)
toexplicit lineItem(QWidget *parent = nullptr)
- remove the unnecessary
lineItem()
constructor - remove the unnecessary
QPushButton *_myPushButton
member - use your own geometry instead of parent's in your logic e.g. instead of
_myPushButton->rect()
userect()
. - override
paintEvent()
and paint there, not in customdraw()
method.
Is there an example ???
Of what? Of exactly what you're doing? I don't think so.
- Change
-
I spied the answer
CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(myButton3 VERSION 0.1 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) set(PROJECT_SOURCES main.cpp mainwindow.cpp mainwindow.h qsliderbutton.cpp qsliderbutton.h mainwindow.ui ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_add_executable(myButton3 MANUAL_FINALIZATION ${PROJECT_SOURCES} ) # Define target properties for Android with Qt 6 as: # set_property(TARGET myButton3 APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR # ${CMAKE_CURRENT_SOURCE_DIR}/android) # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation else() if(ANDROID) add_library(myButton3 SHARED ${PROJECT_SOURCES} ) # Define properties for Android with Qt 5 after find_package() calls as: # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") else() add_executable(myButton3 ${PROJECT_SOURCES} ) endif() endif() target_link_libraries(myButton3 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) set_target_properties(myButton3 PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} ) if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(myButton3) endif()
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QVBoxLayout> #include "qsliderbutton.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; QSliderButton *sldBtn; }; #endif // MAINWINDOW_H
mainwindows.cpp
#include "mainwindow.h" #include "./ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); QVBoxLayout *vlay = new QVBoxLayout(); QHBoxLayout *hlay1 = new QHBoxLayout(); sldBtn = new QSliderButton; hlay1->addWidget(sldBtn); hlay1->addStretch(1); vlay->addItem(hlay1); vlay->addStretch(1); ui->centralwidget->setLayout(vlay); } MainWindow::~MainWindow() { delete ui; }
qsliderbutton.h
#ifndef QSLIDERBUTTON_H #define QSLIDERBUTTON_H #include <QWidget> #include <QMouseEvent> #include <QDebug> class QSliderButton : public QWidget { Q_OBJECT public: explicit QSliderButton(QWidget* parent); QSliderButton(); int getStatus() const; void setStatus(int value); static const int off = 0; static const int on = 1; private: int status = 0; protected: virtual void paintEvent(QPaintEvent *event); virtual QSize sizeHint() const; virtual void mousePressEvent(QMouseEvent * event); }; #endif // QSLIDERBUTTON_H
qsliderbutton.cpp
#include "qsliderbutton.h" #include <QPainter> QSliderButton::QSliderButton(QWidget *parent) { this->setParent(parent); } QSliderButton::QSliderButton() { } int QSliderButton::getStatus() const { return status; } void QSliderButton::setStatus(int value) { status = value; repaint(); } void QSliderButton::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); painter.setPen(QPen(QColor("#fff"), 0.1)); QString bgColorTxt = "#ffffff"; QColor bgColor = QColor(bgColorTxt); painter.setBrush(bgColor); painter.drawRoundedRect(QRectF(0, 0, 50, 20),10,10); QLinearGradient linearGradBtn(QPointF(0, 0),QPointF(16, 16)); QString onColor = "#444"; QColor mainColorOn = QColor(onColor); QColor subColorOn = QColor(onColor); subColorOn.setHsl(0,100,95,0); if (this->status==QSliderButton::on) { QLinearGradient linearGrad(QPointF(32, 2), QPointF(46, 16)); linearGrad.setColorAt(0, subColorOn); linearGrad.setColorAt(1, mainColorOn); painter.setBrush(linearGrad); painter.drawEllipse( QRectF(30, 2, 17, 16) ); } else { QLinearGradient linearGrad(QPointF(2, 2), QPointF(16, 16)); linearGrad.setColorAt(0, subColorOn); linearGrad.setColorAt(1, mainColorOn); painter.setBrush(linearGrad); painter.drawEllipse( QRectF(2, 2, 16, 16) ); } } QSize QSliderButton::sizeHint() const { return QSize(50, 20); } void QSliderButton::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton ) { if (this->status==QSliderButton::on) { this->status = QSliderButton::off; } else { this->status = QSliderButton::on; } repaint(); } }
-
The constructors are still wrong. Should be
QSliderButton::QSliderButton(QWidget* parent) : QWidget(parent) {}
and the empty one is just redundant. You don't need it at all.
Better yet don't manually implement any constructors, just do
class QSliderButton : public QWidget { Q_OBJECT public: using QWidget::QWidget; ...
The using statement will inherit all the constructors from the base class so you don't have to implement any.